<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-855944390206940143</id><updated>2011-10-18T20:35:53.792+02:00</updated><category term='mobile'/><category term='logging'/><category term='protocol'/><category term='DNS'/><category term='googerl'/><category term='libtre'/><category term='hash'/><category term='quick tip'/><category term='regexp'/><category term='parsing'/><category term='functions'/><category term='api'/><category term='sar'/><category term='ghostscript'/><category term='service'/><category term='binary'/><category term='picasa'/><category term='tail'/><category term='sysstat'/><category term='message'/><category term='randomization'/><category term='parallelisation'/><category term='mochiweb'/><category term='gdata'/><category term='googlepages'/><category term='macro'/><category term='unicode'/><category term='digraph'/><category term='posting'/><category term='rant'/><category term='inet_res'/><category term='xml'/><category term='mysql'/><category term='ExtensibleMatch'/><category term='security'/><category term='success'/><category term='openssl'/><category term='nrpe'/><category term='flex'/><category term='webservice'/><category term='rest'/><category term='segfault'/><category term='execution'/><category term='base'/><category term='atom'/><category term='design'/><category term='network'/><category term='external'/><category term='statistics'/><category term='testing'/><category term='error'/><category term='crypto'/><category term='re'/><category term='cean'/><category term='high order functions'/><category term='debugging'/><category term='lists'/><category term='gen_server'/><category term='paging'/><category term='dump'/><category term='hexadecimal'/><category term='benchmarks'/><category term='template'/><category term='http'/><category term='atoms'/><category term='Ldap'/><category term='form'/><category term='download'/><category term='SMTP'/><category term='shell'/><category term='inet_db'/><category term='windows'/><category term='otp'/><category term='port'/><category term='file'/><category term='matching'/><category term='approx match'/><category term='driver'/><category term='variable'/><category term='process'/><category term='stream'/><category term='remote'/><category term='sorting'/><category term='nagios'/><category term='ssh'/><category term='monitoring'/><category term='post'/><category term='join'/><category term='open_port'/><category term='gen_fsm'/><category term='MX record'/><category term='bit syntax'/><category term='user-agent'/><category term='tags'/><category term='blogger'/><category term='synchronisation'/><category term='sql'/><category term='trick'/><category term='ajp13'/><category term='Active Directory'/><category term='jboss'/><category term='Authentication'/><category term='SaxParseException'/><category term='utilities'/><title type='text'>EazyErl !</title><subtitle type='html'>From simple code snippet to full blown module, find what you need and sometimes more :)</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>66</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-5436842906492394236</id><published>2011-10-12T12:53:00.000+02:00</published><updated>2011-10-12T12:53:16.923+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dump'/><category scheme='http://www.blogger.com/atom/ns#' term='file'/><category scheme='http://www.blogger.com/atom/ns#' term='randomization'/><category scheme='http://www.blogger.com/atom/ns#' term='user-agent'/><title type='text'>"Pseudo Randomly" retrieve data...</title><content type='html'>When you want to crawl some websites and you want to hide yourself a little, here's a simple trick to change randomly your user-agent string.&lt;br /&gt;There's a very convenient function in erlang called 'uniform' from the module 'random', you can use it by calling 'random:uniform(MaxValue)' where MaxValue is the high limit.&lt;br /&gt;&lt;br /&gt;So if you want to generate a random value from 1 to 5 you can simple use, 'random:uniform(5)'...&lt;br /&gt;&lt;br /&gt;Now that you know how to generate random values, here's some code that do just what's the title say:&lt;br /&gt;&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;peek_rand(List) -&amp;gt;&lt;br /&gt; Rand = random:uniform(length(List)),&lt;br /&gt; lists:nth(Rand, List).&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This 'peek_rand/1' fun returns the random'th element from the list List.&lt;br /&gt;&lt;br /&gt;Now we can make a simple server loop that will send back a random string to whoever call him:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(List) -&amp;gt;&lt;br /&gt; receive&lt;br /&gt;  {get, Who} -&amp;gt;&lt;br /&gt;   Rand = random:uniform(length(List)),&lt;br /&gt;   Who ! {ua, lists:nth(Rand, List), Rand},&lt;br /&gt;   loop(List);&lt;br /&gt;&lt;br /&gt;  {add, Ua} -&amp;gt;&lt;br /&gt;   loop([Ua|List]);&lt;br /&gt;  &lt;br /&gt;  stop -&amp;gt;&lt;br /&gt;   dump(List),&lt;br /&gt;   exit;&lt;br /&gt;   &lt;br /&gt;  _E -&amp;gt; &lt;br /&gt;   loop(List)&lt;br /&gt; end.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We have also implemented a message that let you store new strings into the main list, look at the message '{add, Ua}' to understand...&lt;br /&gt;&lt;br /&gt;To summup here, we are able to randomly peek a string from a list, and give it back to some other process...&lt;br /&gt;Wouldn't be nice if we were able to store this list of strings ?&lt;br /&gt;Of course yes !&lt;br /&gt;&lt;br /&gt;So let's look at the 'io:format/3' fun. We know that the format string can contain the '~p' that basically express in a simple form any erlang term, and more precisely a List...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt; &lt;br /&gt;dump(List) -&amp;gt;&lt;br /&gt; {ok, Fd} = file:open("ua.list", [write]),&lt;br /&gt; io:format(Fd, "~p.~n", [List]),&lt;br /&gt; file:close(Fd).&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And this is a convenient way to retrieve the data written in the 'ua.list':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;load() -&amp;gt;&lt;br /&gt; {ok, [List|_]} = file:consult("ua.list"),&lt;br /&gt; List.&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-5436842906492394236?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/5436842906492394236/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=5436842906492394236' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5436842906492394236'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5436842906492394236'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2011/10/pseudo-randomly-retrieve-data.html' title='&quot;Pseudo Randomly&quot; retrieve data...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1145348141157059748</id><published>2010-07-29T14:58:00.010+02:00</published><updated>2011-10-12T12:49:40.677+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quick tip'/><category scheme='http://www.blogger.com/atom/ns#' term='debugging'/><category scheme='http://www.blogger.com/atom/ns#' term='macro'/><title type='text'>erlang: Simple Debug macro</title><content type='html'>When testing things I need some quick way to debug "a la" printf() style. Old habits :)&lt;br /&gt;&lt;br /&gt;Here's is the simple debug macro I use:&lt;br /&gt;&lt;pre&gt;-ifdef(debug).&lt;br /&gt;-define(DEBUG(Format, Args),&lt;br /&gt;  io:format("~s.~w: DEBUG: " ++ Format, [ ?MODULE, ?LINE | Args])).&lt;br /&gt;-else.&lt;br /&gt;-define(DEBUG(Format, Args), true).&lt;br /&gt;-endif.&lt;br /&gt;&lt;/pre&gt;Write it down in a "debug.hrl" file, then you only need to add this line in any file header:&lt;br /&gt;&lt;pre&gt;-include("debug.hrl").&lt;br /&gt;&lt;/pre&gt;This simple macro gives you the module name and the line number. This saves me a lot of time.&lt;br /&gt;&lt;br /&gt;Then you need to define the "debug" atom to let your macro do what you want. The compile:file/2 handles options for this, the syntax is &lt;a href="http://erldocs.com/R14A/compiler/compile.html?i=5&amp;amp;search=compile#file/2"&gt;{d, debug}&lt;/a&gt;&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You can also use some simple helper module to compile multiples files using simple regexp:&lt;br /&gt;Here's the code: &lt;br /&gt;&lt;pre&gt;-module(utils_compile).&lt;br /&gt;&lt;br /&gt;-export([c/1, c/2, d/1, d/2]).&lt;br /&gt;&lt;br /&gt;c(FilePattern) -&amp;gt;&lt;br /&gt; c(FilePattern, "../ebin").&lt;br /&gt;&lt;br /&gt;c(FilePattern, OutDir) -&amp;gt;&lt;br /&gt; filelib:fold_files(".", FilePattern ++ ".erl$", false, &lt;br /&gt; fun(X, _Acc) -&amp;gt; &lt;br /&gt;  io:format("Compiling ~p~n", [ X ]), &lt;br /&gt;  compile:file(X, [{outdir, OutDir}, report]) end, []).&lt;br /&gt;&lt;br /&gt;d(FilePattern) -&amp;gt;&lt;br /&gt; d(FilePattern, "../ebin").&lt;br /&gt;&lt;br /&gt;d(FilePattern, OutDir) -&amp;gt;&lt;br /&gt; filelib:fold_files(".", FilePattern ++ ".erl$", false, &lt;br /&gt; fun(X, _Acc) -&amp;gt; &lt;br /&gt;  io:format("Compiling (debug) ~p~n", [ X ]), &lt;br /&gt;  compile:file(X, [{outdir, OutDir}, {d, debug}, report]) end, []).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1145348141157059748?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1145348141157059748/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1145348141157059748' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1145348141157059748'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1145348141157059748'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2010/07/simple-debug-macro.html' title='erlang: Simple Debug macro'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-8276989122488926253</id><published>2010-02-24T09:00:00.007+01:00</published><updated>2010-02-24T09:00:02.004+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='regexp'/><category scheme='http://www.blogger.com/atom/ns#' term='lists'/><title type='text'>Filtering lines efficiently</title><content type='html'>Whenever you are dealing with log lines or that you're program is filtering data you always have to handle 'escaping' efficiently.&lt;br /&gt;&lt;br /&gt;While developing a log module using a gen_event, I needed to escape simple quotes.&lt;br /&gt;Sometime thoses quotes were already escaped...&lt;br /&gt;&lt;br /&gt;I've found this regexp to handle gracefully the case:&lt;br /&gt;&lt;pre&gt;re:replace( Bin, "(?&amp;lt;!\\\\)'", "\\\\'", [ global ] ).&lt;br /&gt;&lt;/pre&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;Not so easy to read, and because of the various backslashes, this regexp needed some tests&lt;br /&gt;before being fully usable.&lt;br /&gt;Basically this regexp only filter simple quote when they're not already escaped...&lt;br /&gt;&lt;br /&gt;Now that I've my filter for quotes and I need a filter for newline characters.&lt;br /&gt;Everytime you find some newlines in your log files, you can be sure that many tools already in your network will not treat them efficiently, worse this may break everything after ...&lt;br /&gt;&lt;br /&gt;So I came across this regexp:&lt;br /&gt;&lt;pre&gt;re:replace( Bin, "[\\n\\r]+", " ", [ global ] ).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally having two filter functions for every line, I wanted to be able to add or remove easily any function.&lt;br /&gt;You can reference functions with this notation:&lt;br /&gt;&lt;pre&gt;fun filterquotes/1&lt;br /&gt;&lt;/pre&gt;Or a list of functions:&lt;br /&gt;&lt;pre&gt;[ fun filterquotes/1, fun filternewlines/1 ]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With this notation and the famous 'lists:foldl', I can filter a line with many functions quite nicely:&lt;br /&gt;&lt;pre&gt;% Data is the accumulator, but we don't change it :)&lt;br /&gt;        lists:foldl( fun( Fun, Data ) -&gt;&lt;br /&gt;                         Fun(Data) &lt;br /&gt;        end, Bin, [ fun filterquotes/1, fun filternewline/1 ]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's the code:&lt;br /&gt;&lt;pre&gt;filter( Bin ) when is_atom(Bin) -&gt;&lt;br /&gt;        Bin;&lt;br /&gt;filter( Bin ) when is_integer(Bin) -&gt;&lt;br /&gt;        Bin;&lt;br /&gt;filter( Bin ) -&gt;&lt;br /&gt;        lists:foldl( fun( Fun, Data ) -&gt;&lt;br /&gt;                         Fun(Data) &lt;br /&gt;        end, Bin, [ fun filterquotes/1, fun filternewline/1 ]).&lt;br /&gt;&lt;br /&gt;filterquotes(Bin) -&gt;&lt;br /&gt;        re:replace( Bin, "(?&lt;!\\\\)'", "\\\\'", [ global ] ).&lt;br /&gt;&lt;br /&gt;filternewline( Bin ) -&gt;&lt;br /&gt;        re:replace( Bin, "[\\n\\r]+", " ", [ global ] ).&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-8276989122488926253?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/8276989122488926253/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=8276989122488926253' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8276989122488926253'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8276989122488926253'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2010/02/filtering-lines-efficiently.html' title='Filtering lines efficiently'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-5533665012407705943</id><published>2010-02-23T21:08:00.001+01:00</published><updated>2010-02-23T22:23:26.043+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crypto'/><category scheme='http://www.blogger.com/atom/ns#' term='openssl'/><title type='text'>Reading an openssl .priv.key file and extracting the key</title><content type='html'>Extracting the private key from a .priv.key file is simple.&lt;br /&gt;The private key is encrypted using a AES-128 with your passphrase.&lt;br /&gt;&lt;br /&gt;The initial vector is also stored in the file, you can extract it directly from the first line:&lt;br /&gt;&lt;pre&gt;get_salt( &lt;&lt;"Salted__", Salt:8/binary, Rest/binary&gt;&gt; ) -&gt;&lt;br /&gt;        {Salt, Rest}.&lt;br /&gt;&lt;/pre&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;The last part is handled by some md5() of your passphrase and the initial vector:&lt;br /&gt;&lt;pre&gt;Key = crypto:md5([ Password, Salt ]),&lt;br /&gt;        IV = crypto:md5([ Key, Password, Salt ]),&lt;br /&gt;        crypto:aes_cbc_128_decrypt( Key, IV, Rest).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now the full module:&lt;br /&gt;&lt;pre&gt;-module(priv_key).&lt;br /&gt;&lt;br /&gt;-compile(export_all).&lt;br /&gt;&lt;br /&gt;priv_key_file( File, Password ) -&gt;&lt;br /&gt;        {ok, Bin} = file:read_file(File),&lt;br /&gt;        {Salt, Rest} = get_salt(Bin),&lt;br /&gt;        Key = crypto:md5([ Password, Salt ]),&lt;br /&gt;        IV = crypto:md5([ Key, Password, Salt ]),&lt;br /&gt;        crypto:aes_cbc_128_decrypt( Key, IV, Rest).&lt;br /&gt;        &lt;br /&gt;get_salt( &lt;&lt;"Salted__", Salt:8/binary, Rest/binary&gt;&gt; ) -&gt;&lt;br /&gt;        {Salt, Rest}.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-5533665012407705943?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/5533665012407705943/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=5533665012407705943' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5533665012407705943'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5533665012407705943'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2010/02/reading-openssl-privkey-file-and.html' title='Reading an openssl .priv.key file and extracting the key'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6205237506739203832</id><published>2009-09-22T22:34:00.002+02:00</published><updated>2009-09-22T22:37:14.381+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='parsing'/><title type='text'>erlang: Parsing binary data dynamically</title><content type='html'>Hi, &lt;br /&gt;here's a quick tip for parsing binary data which format is unknown at compile time...&lt;br /&gt;&lt;br /&gt;Let's say that you have a binary string and that later you receive its structure. Take for&lt;br /&gt;example the code below:&lt;br /&gt;&lt;pre&gt;-module(binm).&lt;br /&gt;&lt;br /&gt;-export([test/0, test/2]).&lt;br /&gt;&lt;br /&gt;test() -&gt;&lt;br /&gt;        test(&amp;lt;&amp;lt;4,0,0,0,5,0,0,0,7,0,8,0,33,1&gt;&gt;, [ 4, 4, 2, 2]).&lt;br /&gt;&lt;br /&gt;test(Bin, List) -&gt;&lt;br /&gt;        {Final, End} = lists:foldl( fun(Len, {Res, Rest}) -&gt;&lt;br /&gt;                case Rest of &lt;br /&gt;                        &amp;lt;&amp;lt;M:Len/binary, NewRest/binary&gt;&gt; -&gt;&lt;br /&gt;                                {[ M | Res ], NewRest};&lt;br /&gt;                        &amp;lt;&amp;lt;_:1/binary, NewRest/binary&gt;&gt; -&gt;&lt;br /&gt;                                { Res, NewRest}&lt;br /&gt;                end&lt;br /&gt;                end, {[], Bin}, List),&lt;br /&gt;        {lists:reverse(Final), End}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Precisely, we want to slice the binary part into 4 parts described as '[4, 4, 2, 2]' where each element is the size.&lt;br /&gt;&lt;pre&gt;test() -&gt;&lt;br /&gt;        test(&amp;lt;&amp;lt;4,0,0,0,5,0,0,0,7,0,8,0,33,1&gt;&gt;, [ 4, 4, 2, 2]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's compile and run:&lt;br /&gt;&lt;pre&gt;2&gt; c(binm).    &lt;br /&gt;{ok,binm}&lt;br /&gt;3&gt; binm:test().&lt;br /&gt;{[&amp;lt;&amp;lt;4,0,0,0&gt;&gt;,&amp;lt;&amp;lt;5,0,0,0&gt;&gt;,&amp;lt;&amp;lt;7,0&gt;&gt;,&amp;lt;&amp;lt;8,0&gt;&gt;],&amp;lt;&amp;lt;33,1&gt;&gt;}&lt;br /&gt;4&gt; &lt;br /&gt;&lt;/pre&gt;Isn't this nice ? :p&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6205237506739203832?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6205237506739203832/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6205237506739203832' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6205237506739203832'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6205237506739203832'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2009/09/erlang-parsing-binary-data-dynamically.html' title='erlang: Parsing binary data dynamically'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1248311764881262029</id><published>2009-08-18T22:42:00.005+02:00</published><updated>2011-10-12T12:59:36.066+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='high order functions'/><category scheme='http://www.blogger.com/atom/ns#' term='regexp'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='re'/><title type='text'>erlang: testing many conditions easily with lists of funs...</title><content type='html'>Sometimes you have to test many things before being able to choose the next action...&lt;br /&gt;&lt;br /&gt;In many languages, you'll end up using a bunch of  "if then else". &lt;br /&gt;But in erlang, and the power of fun()s, you can efficiently write a simple function that will do all the job for you :p&lt;br /&gt;&lt;br /&gt;Here is our purpose: call many functions with one argument.&lt;br /&gt;For this example, we need to determine a file type with its filename.&lt;br /&gt;&lt;br /&gt;Let's say that:&lt;br /&gt;- the filename could be a valid 'word' temporary file, &lt;br /&gt;- or a 'excel' temporary file or &lt;br /&gt;- a known file type.&lt;br /&gt;&lt;br /&gt;Firts let's define a simple fun that takes a list of fun and stop evaluating those fun as soon as a result is found:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;% the simple case where the list is empty&lt;br /&gt;any(_, []) -&amp;gt; undefined;&lt;br /&gt;&lt;br /&gt;% the general case when the list contains funs...&lt;br /&gt;any(Arg, [ {F, PrepareFun} | Funs] ) -&amp;gt;&lt;br /&gt;        case F( PrepareFun(Arg) ) of&lt;br /&gt;                undefined -&amp;gt;&lt;br /&gt;                        any(Arg, Funs);&lt;br /&gt;&lt;br /&gt;                _V -&amp;gt;&lt;br /&gt;                        _V&lt;br /&gt;        end.&lt;/pre&gt;&lt;br /&gt;In this code you'll notice that there are two fun()s:&lt;br /&gt;- the 'F', &lt;br /&gt;- the 'PrepareFun'. &lt;br /&gt;The idea is that 'PrepareFun' will be called before calling 'F' to filter the argument 'Arg'. &lt;br /&gt;Imagine that sometimes you need to extract the basename from the filename, or whatever else...&lt;br /&gt;&lt;br /&gt;The code is a simple list iteration that recurse only if the result of the function call is 'undefined'. &lt;br /&gt;&lt;br /&gt;Now that we have a valid fun that can iterate over a list of funs and stop whenever a valid result is found (or end of list), let's get back to our example, and build our 'filetype' function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;fileType(File) -&amp;gt;&lt;br /&gt;        any( File, [ &lt;br /&gt;                        {fun word_temp/1, fun filename:basename/1}, &lt;br /&gt;                        {fun db_extension/1, fun lists:reverse/1},&lt;br /&gt;                        {fun excel_temp/1, fun filename:basename/1}&lt;br /&gt;                ]).&lt;/pre&gt;&lt;br /&gt;You can read this code like this: &lt;br /&gt;"any of the funs from the list may determine the type of the file". &lt;br /&gt;And once found, stops.&lt;br /&gt;&lt;br /&gt;Let's describe those called functions 'db_extension/1', 'word_temp/1', 'excel_temp/1'...&lt;br /&gt;&lt;br /&gt;First 'db_extension':&lt;br /&gt;You'll notice that we test only the end of the filename, that's why the filename is&lt;br /&gt;reversed before being passed to the function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;db_extension( "pmt."  ++ _ ) -&amp;gt; temp;&lt;br /&gt;db_extension( "PMT."  ++ _ ) -&amp;gt; temp;&lt;br /&gt;db_extension( "xcod." ++ _ ) -&amp;gt; doc;&lt;br /&gt;db_extension( "cod."  ++ _ ) -&amp;gt; doc;&lt;br /&gt;db_extension( "xslx." ++ _ ) -&amp;gt; xls;&lt;br /&gt;db_extension( "slx."  ++ _ ) -&amp;gt; xls;&lt;br /&gt;db_extension( "xtpp." ++ _ ) -&amp;gt; ppt;&lt;br /&gt;db_extension( "tpp." ++ _ ) -&amp;gt; ppt;&lt;br /&gt;db_extension( _ ) -&amp;gt; undefined.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The 'word_temp/1' need to call the basename of the file but we don't need the full path, so 'PrepareFun' is simply 'filename:basename/1' in this case:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;word_temp( "~$"   ++ _) -&amp;gt; temp;&lt;br /&gt;word_temp( "~WRD" ++ _) -&amp;gt; temp;&lt;br /&gt;word_temp( "~WRL" ++ _) -&amp;gt; temp;&lt;br /&gt;word_temp( _ ) -&amp;gt; undefined.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For 'excel_temp/1', the temp file is determined by a number written as 8 hexadecimal values. We use the re module to easily match this with the filename. In this case the 'PrepareFun' is also the 'filename:basename/1':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;excel_temp( File ) -&amp;gt;&lt;br /&gt;        ReList = [ &amp;lt;&amp;lt;"^[0-9A-Z]{8}$"&amp;gt;&amp;gt; ],&lt;br /&gt;        do_re(File, ReList).&lt;br /&gt;        &lt;br /&gt;% We are able to test many re but in the specific&amp;nbsp;&lt;/pre&gt;&lt;pre&gt;% case the list contains only one element...&lt;br /&gt;do_re(_, []) -&amp;gt; undefined;&lt;br /&gt;do_re(Subject, [ Re | Rest ]) -&amp;gt;&lt;br /&gt;        case re:run(Subject, Re, [{capture,none}]) of&lt;br /&gt;                nomatch -&amp;gt;&lt;br /&gt;                        do_re(Subject, Rest);&lt;br /&gt;&lt;br /&gt;                match -&amp;gt;&lt;br /&gt;                        temp&lt;br /&gt;        end.&lt;/pre&gt;&lt;br /&gt;From the re module, options "capture none" is used to only returns if the re match, and not the part that successfully match... &lt;br /&gt;(this is  simple optimisation, since we don't care about the matching part)&lt;br /&gt;&lt;br /&gt;If we look at back at what we've done here, we can see that &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;fileType(File) -&amp;gt;&lt;br /&gt;        any( File, [ &lt;br /&gt;                        {fun word_temp/1, fun filename:basename/1}, &lt;br /&gt;                        {fun db_extension/1, fun lists:reverse/1},&lt;br /&gt;                        {fun excel_temp/1, fun filename:basename/1}&lt;br /&gt;                ]).&lt;/pre&gt;&lt;br /&gt;can really easily extended with other functions, as long as those new functions take only one parameter...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;fileType(File) -&amp;gt;&lt;br /&gt;        any( File, [ &lt;br /&gt;                        {fun word_temp/1, fun filename:basename/1}, &lt;br /&gt;                        {fun db_extension/1, fun lists:reverse/1},&lt;br /&gt;                        {fun excel_temp/1, fun filename:basename/1},&lt;br /&gt;                        {fun firefox_temp/1, fun filename:basename/1},&lt;br /&gt;                        {fun directory_temp/1, fun(X) -&amp;gt; X end}&lt;br /&gt;                ]).&lt;/pre&gt;&lt;br /&gt;Conclusion:&lt;br /&gt;Building list of functions is an efficient way of "testing many conditions".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1248311764881262029?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1248311764881262029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1248311764881262029' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1248311764881262029'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1248311764881262029'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2009/08/erlang-testing-many-conditions-easily.html' title='erlang: testing many conditions easily with lists of funs...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1459934450611157840</id><published>2009-08-18T22:01:00.006+02:00</published><updated>2011-10-12T12:58:06.818+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='service'/><category scheme='http://www.blogger.com/atom/ns#' term='logging'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><title type='text'>erlang: how to make a windows service</title><content type='html'>Tired of fighting with the command line to make &lt;b&gt;erlsrv&lt;/b&gt; work ?&lt;br /&gt;I have a solution for you !&lt;br /&gt;The problem are always the quotes, you have quotes for erlang and quotes for the windows command line...&lt;br /&gt;Here's what I use to test my service:&lt;br /&gt;&lt;br /&gt;(Pack everything  in a simple "install.bat")&lt;br /&gt;&lt;code&gt;&lt;br /&gt;erlsrv remove "YourService"&lt;br /&gt;erlsrv add "YourService" -stopaction "init:stop()." -sname Service -debugtype reuse -args "-kernel error_logger {file,\\""C:/Test/kernel.txt\\""} -setcookie YourCookie -s YourInit"&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;YourInit is the name of the module you want to start. The fun "start/0" will be called by "erl".&lt;br /&gt;&lt;br /&gt;This install.bat is meant to be your debug version of your service, because the log file will grow indefinitely.&lt;br /&gt;&lt;br /&gt;See the &lt;a href="http://erlang.org/doc/apps/erts/index.html"&gt;documentation&lt;/a&gt; for more information:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;DebugType: Can be one of none (default), new, reuse or console. Specifies that output from the Erlang shell should be sent to a "debug log". The log file is named "servicename".debug or "servicename".debug."n", where "n" is an integer between 1 and 99. The log-file is placed in the working directory of the service (as specified in WorkDir). The reuse option always reuses the same log file ("servicename".debug) and the new option uses a separate log file for every invocation of the service ("servicename".debug."n"). The console option opens an interactive Windows® console window for the Erlang shell of the service. &lt;br /&gt;The console option automatically disables the StopAction and a service started with an interactive console window will not survive logouts, OnFail actions do not work with debug-consoles either. If no DebugType is specified (none), the output of the Erlang shell is discarded.&lt;br /&gt;The consoleDebugType is not in any way intended for production. It is only a convenient way to debug Erlang services during development. The new and reuse options might seem convenient to have in a production system, but one has to take into account that the logs will grow indefinitely during the systems lifetime and there is no way, short of restarting the service, to truncate those logs. In short, the DebugType is intended for debugging only. Logs during production are better produced with the standard Erlang logging facilities.&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;If you don't define the "WorkDir" (-w option) your debug file will be located in the "WINDOWS\system32" directory.&lt;br /&gt;&lt;br /&gt;Finally, the service will be described in the registry in &lt;br /&gt;&lt;code&gt;&lt;br /&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Ericsson\Erlang\ErlSrv\1.1\YourService&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1459934450611157840?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1459934450611157840/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1459934450611157840' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1459934450611157840'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1459934450611157840'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2009/08/erlang-how-to-make-windows-service.html' title='erlang: how to make a windows service'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-4474140597748772871</id><published>2009-08-17T22:10:00.004+02:00</published><updated>2009-08-18T21:49:46.137+02:00</updated><title type='text'>erlang: Extracting values from binary streams with macros</title><content type='html'>Writing a lot of binary matching strings, I now use simple macros to synchronise erlang code with others language...&lt;br /&gt;Let me explain a bit, there were many lines who look like theses:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;parse(&lt;&lt; Id:32/little-unsigned, Oid:32/little-unsigned, Soid:16/little-unsigned &gt;&gt;, State) -&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now I really prefer to see lines looking like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;parse(&lt;&lt; ?UINT32( Id ),&lt;br /&gt;                  ?UINT32( Oid ),&lt;br /&gt;                  ?UINT16( Soid ) &gt;&gt;, State) -&gt;&lt;br /&gt; ...&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The magic trick was to define macros at the beginning of the erl module like this:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;-define( UINT32(X),  X:32/little-unsigned).&lt;br /&gt;-define( UINT16(X),  X:16/little-unsigned).&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now everyone can read those parse lines easily...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-4474140597748772871?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/4474140597748772871/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=4474140597748772871' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4474140597748772871'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4474140597748772871'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2009/08/erlang-extracting-values-from-binary.html' title='erlang: Extracting values from binary streams with macros'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6524554699765635992</id><published>2009-08-17T21:56:00.005+02:00</published><updated>2009-09-06T16:11:24.633+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='file'/><category scheme='http://www.blogger.com/atom/ns#' term='unicode'/><category scheme='http://www.blogger.com/atom/ns#' term='utilities'/><title type='text'>erlang: Unicode support for your filenames...</title><content type='html'>From R13B you have full unicode support for strings.&lt;br /&gt;&lt;br /&gt;I'm involved in some kind of interface between the windows kernel and an erlang vm, and I find this "unicode" module really&lt;br /&gt;helpful.&lt;br /&gt;&lt;br /&gt;For your information, internally every file path or file name is encoded as a little endian utf16 string in the windows kernel.&lt;br /&gt;Exchanging information between those two world means that you'll have to convert utf16 into ansi strings.&lt;br /&gt;&lt;br /&gt;For example you can create an utf16 binary string using this&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;unicode:characters_to_binary("your string" latin1, {utf16,little}).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This means that your string is "latin1" and you want a binary utf16 little endian encoded.&lt;br /&gt;Really easy !&lt;br /&gt;&lt;br /&gt;Here's some free code that let you easily manipulate file paths and filenames...&lt;br /&gt;I hope this will help someone :p&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(filename_utils).&lt;br /&gt;&lt;br /&gt;-export([extension/1, basename/1, dirname/1]).&lt;br /&gt;-export([normalize/1, utf16toansi/1]).&lt;br /&gt;-export([test/1]).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;extension(Bin) -&gt;&lt;br /&gt;        filename:extension( utf16toansi(Bin) ).&lt;br /&gt;&lt;br /&gt;basename(Bin) -&gt;&lt;br /&gt;        filename:basename( utf16toansi(Bin) ).&lt;br /&gt;&lt;br /&gt;dirname(Bin) -&gt;&lt;br /&gt;        filename:nativename(  filename:dirname( utf16toansi(Bin) ) ).&lt;br /&gt;&lt;br /&gt;test(Mode) -&gt;&lt;br /&gt;        Word = "C:\\Program Files\\WINWORD.EXE",&lt;br /&gt;        File = unicode:characters_to_binary(Word, latin1, {utf16,little}),&lt;br /&gt;        ?MODULE:Mode( File ).&lt;br /&gt;&lt;br /&gt;utf16toansi(Bin) -&gt;&lt;br /&gt;        unicode:characters_to_list(Bin, {utf16,little}).&lt;br /&gt;&lt;br /&gt;normalize(File) when is_list(File) -&gt;&lt;br /&gt;        Path = filename:dirname(File),&lt;br /&gt;        Base = filename:basename(File),&lt;br /&gt;        Ext = filename:extension(File),&lt;br /&gt;        {Base, Path, Ext};&lt;br /&gt;&lt;br /&gt;normalize(Bin) when is_binary(Bin) -&gt;&lt;br /&gt;        Path = dirname(Bin),&lt;br /&gt;        Base = basename(Bin),&lt;br /&gt;        Ext = extension(Bin),&lt;br /&gt;        {Base, Path, Ext}.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6524554699765635992?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6524554699765635992/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6524554699765635992' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6524554699765635992'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6524554699765635992'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2009/08/unicode-support-for-your-filenames.html' title='erlang: Unicode support for your filenames...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3317015752874099332</id><published>2008-11-03T09:06:00.004+01:00</published><updated>2008-11-03T09:08:51.781+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cean'/><title type='text'>CEAN 1.4 is released</title><content type='html'>Tdoay is a great day, today you can read on the erlang mailling list that "&lt;a href="http://cean.process-one.net/"&gt;CEAN 1.4 is released&lt;/a&gt;" ! ( this time it's R12B4 based release )&lt;br /&gt;There's also a new website, new design and new Cean packages.&lt;br /&gt;&lt;br /&gt;Go grab it !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3317015752874099332?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3317015752874099332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3317015752874099332' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3317015752874099332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3317015752874099332'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/11/cean-14-is-released.html' title='CEAN 1.4 is released'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6059775959146820478</id><published>2008-10-18T19:57:00.004+02:00</published><updated>2008-10-19T12:21:02.986+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crypto'/><category scheme='http://www.blogger.com/atom/ns#' term='hash'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>Secure Cookies for your web application...</title><content type='html'>Now that new erlang web framework &lt;a href="http://www.reddit.com/r/programming/comments/77gsj/nitrogen_eventdriven_web_framework_in_erlang/"&gt;are here&lt;/a&gt;, I think that sessions are still today a weakness. &lt;br /&gt;&lt;br /&gt;Session and Cookies must be secure, there's no single day without some new vulnerability about session hijacking. &lt;br /&gt;&lt;br /&gt;That's why very clever people design the &lt;a href="http://www.cse.msu.edu/~alexliu/publications/Cookie/cookie.pdf"&gt;secure cookie protocol [PDF]&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Here's the Cookie value: &lt;pre&gt;user name|expiration time|(data)k|HMAC( user name|expiration time|data|session key, k)&lt;/pre&gt;&lt;br /&gt;where &lt;pre&gt;k=HMAC(user name|expiration time, sk)&lt;/pre&gt;&lt;br /&gt;and where sk is a secret key&lt;br /&gt;&lt;br /&gt;Now you can verify the cookie using theses techniques:&lt;br /&gt;&lt;pre&gt;1. Compare the cookie’s expiration time and the server’s current&lt;br /&gt;  time. If the cookie has expired, then return FALSE.&lt;br /&gt;2. Compute the encryption key as follows:&lt;br /&gt; k=HMAC(user name|expiration time, sk)&lt;br /&gt;3. Decrypt the encrypted data using k.&lt;br /&gt;4. Compute HMAC(user name|expiration time|data|session key, k),&lt;br /&gt; and compare it with the keyed-hash message authentication code&lt;br /&gt; of the cookie. If they match, then return TRUE;&lt;br /&gt; otherwise return FALSE.&lt;br /&gt; TRUE&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here's the erlang module&lt;br /&gt;&lt;pre&gt;-module(scookies).&lt;br /&gt;-export([start/0, gen_auth/1, gen_build/2, gen_check/2, read/1, check/4, test/0]).&lt;br /&gt;-export([message/1]).&lt;br /&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt; application:start(crypto).&lt;br /&gt;&lt;br /&gt;gen_build(ServerKey, IVec) -&gt;&lt;br /&gt; fun(Username, D, SessionKey) -&gt;&lt;br /&gt;  Expiration = integer_to_list(1212559656),&lt;br /&gt;  Key = crypto:md5_mac( [Username, Expiration], ServerKey), %16bytes&lt;br /&gt;  Data = crypto:aes_cbc_128_encrypt(Key, IVec, D),&lt;br /&gt;  Hmac = crypto:sha_mac([Username, Expiration, Data, SessionKey], Key),&lt;br /&gt;  io:format("Build: ~p ~p ~p ~p ~p~n",[Username, Expiration, Data, SessionKey, Key]),&lt;br /&gt;  iolist_to_binary([ Username, $,, Expiration, $,, Data, $,, Hmac ])&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;read(Cookie) -&gt;&lt;br /&gt; {A, B, C} = Cookie,&lt;br /&gt; {A, B, C}.&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;gen_check(ServerKey, IVec) -&gt;&lt;br /&gt;  fun(Cookie, SessionKey) -&gt;&lt;br /&gt;  [ Username, Expiration, Crypted, Hmac ] = string:tokens(binary_to_list(Cookie), ","),&lt;br /&gt;  Key = crypto:md5_mac([ Username, Expiration ], ServerKey),&lt;br /&gt;  Data = crypto:aes_cbc_128_decrypt(Key, IVec, Crypted),&lt;br /&gt;  MAC = crypto:sha_mac([ Username, Expiration, Crypted, SessionKey], Key),&lt;br /&gt;  io:format("Check: ~p ~p ~p ~p ~p~n",[Username, Expiration, Data, SessionKey, Key]),&lt;br /&gt;  &lt;&amp;lt;Len:16,Message:Len/binary,_/binary&gt;&gt; = Data,&lt;br /&gt;  io:format("Decrypted: ~p '~s'~n'~p'~n'~p'~n", [Len, Message, MAC, list_to_binary(Hmac)]),&lt;br /&gt;  [ Username, Expiration, {Len, Message}, MAC, list_to_binary(Hmac)]&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;% Returns the build fun and check fun &lt;br /&gt;% This is a helper fun to let you build in q simple way bot the build fun and&lt;br /&gt;% the decode fun...&lt;br /&gt;gen_auth(ServerKey) -&gt; &lt;br /&gt; IVec = &lt;&lt;"3985928509201031"&gt;&gt;, %16bytes Must be Random&lt;br /&gt; [ gen_build(ServerKey, IVec), gen_check(ServerKey, IVec) ].&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;check(Cookie, ServerKey, InitVec, SessionKey) -&gt;&lt;br /&gt; {Username, ExpirationTime, Crypted, CookieMAC} = read(Cookie),&lt;br /&gt; case check_time(ExpirationTime) of % see later check_time...&lt;br /&gt;  ok -&gt;&lt;br /&gt;   Key = crypto:sha_mac([ Username, ExpirationTime ], ServerKey),&lt;br /&gt;   Data = crypto:aes_cbc_128_decrypt(Key, InitVec, Crypted),&lt;br /&gt;   MAC = crypto:sha_mac([ Username, ExpirationTime, Data, SessionKey], Key),&lt;br /&gt;   compare(MAC, CookieMAC, Data);&lt;br /&gt;&lt;br /&gt;  _E -&gt; &lt;br /&gt;   {error, _E}&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;compare(_A, _A, Data) -&gt;&lt;br /&gt; {ok, Data};&lt;br /&gt;compare(_A, _B, _Data) -&gt;&lt;br /&gt; {error, nomatch}.&lt;br /&gt;&lt;br /&gt;check_time(1212559656) -&gt; % It's up to you to set it &lt;br /&gt; true;&lt;br /&gt;check_time(_) -&gt;&lt;br /&gt; false.&lt;br /&gt;&lt;br /&gt;message(Text) -&gt;&lt;br /&gt; Len = size(Text),&lt;br /&gt; Pad = 64 - Len - 2,&lt;br /&gt; &lt;&amp;lt;Len:16,Text/binary, 0:Pad/unit:8&gt;&gt;.&lt;br /&gt; &lt;br /&gt;test() -&gt;&lt;br /&gt; ServerKey = &lt;&lt;"serverkey"&gt;&gt;,&lt;br /&gt; SessionKey = &lt;&lt;"3ID409a0sd09"&gt;&gt;,&lt;br /&gt; [ Enc, Dec ] = gen_auth(ServerKey),&lt;br /&gt; CCookie = Enc("rolphin", message(&lt;&lt;"stream/128693"&gt;&gt;), SessionKey), &lt;br /&gt; DCookie = Dec(CCookie, SessionKey),&lt;br /&gt; io:format("C: ~s~n", [ CCookie ]),&lt;br /&gt; display(DCookie).&lt;br /&gt;&lt;br /&gt;display([Username, Expiration, {Len, Message}, _Mac, _Mac]) -&gt;&lt;br /&gt; io:format("Message ok: ~s (~s) ~p: '~s'~n", [Username, Expiration, Len, Message]);&lt;br /&gt;display([Username, Expiration, {Len, Message}, _Mac, _OtherMac]) -&gt;&lt;br /&gt; io:format("Invalid Mac ! ~s (~s) ~p: '~s'~n", [Username, Expiration, Len, Message]).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6059775959146820478?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6059775959146820478/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6059775959146820478' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6059775959146820478'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6059775959146820478'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/10/secure-cookies-for-your-web-application.html' title='Secure Cookies for your web application...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-5648745634610426206</id><published>2008-10-09T18:27:00.003+02:00</published><updated>2008-10-09T18:31:43.022+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='benchmarks'/><title type='text'>Benchmarking must be done carefully !</title><content type='html'>An example is better than a long post !&lt;br /&gt;&lt;br /&gt;Timeout were hidden, smp by default modified the expected behaviour, and more...&lt;br /&gt;Don't assume things, just take time to verify :)&lt;br /&gt;&lt;br /&gt;Explained &lt;a href="http://www.erlang.org/pipermail/erlang-questions/2008-October/038944.html"&gt;here&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;From the beginning:&lt;br /&gt;started &lt;a href="http://www.erlang.org/pipermail/erlang-questions/2008-October/038911.html"&gt;here&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-5648745634610426206?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/5648745634610426206/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=5648745634610426206' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5648745634610426206'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5648745634610426206'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/10/benchmarking-must-be-done-carefully.html' title='Benchmarking must be done carefully !'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3742344622512441331</id><published>2008-09-28T20:34:00.007+02:00</published><updated>2008-10-03T14:01:16.556+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gen_fsm'/><category scheme='http://www.blogger.com/atom/ns#' term='bit syntax'/><category scheme='http://www.blogger.com/atom/ns#' term='SMTP'/><category scheme='http://www.blogger.com/atom/ns#' term='MX record'/><title type='text'>gen_fsm vs a simple fsm for sending emails...</title><content type='html'>I needed to send emails, and check for emails validity. &lt;br /&gt;I choose to use at the beginning the gen_fsm used to send email, for &lt;br /&gt;example this &lt;a href="http://www.trapexit.org/forum/viewtopic.php?p=44030"&gt;one&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I found that I just need a simple fun that just send a mail, and don't want a full gen_fsm that need to be called for every steps involved in sending a simple mail...&lt;br /&gt;&lt;br /&gt;I needed a way to mail efficiently, by efficiently I mean:&lt;br /&gt; - the mx server will be contacted directly&lt;br /&gt; - if one mx server fail try another one&lt;br /&gt; - handle the greylisting transparently, ( don't block the send queue for one message )&lt;br /&gt;&lt;br /&gt;My fun then becomes:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;start_link(Server,Port,ServerName,MailFrom,To,Data) -&gt;&lt;br /&gt;  spawn_link(?MODULE, init, [Server,Port,ServerName,MailFrom,To,Data]).&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;And my module gets a name 'smtp_client'.&lt;br /&gt;&lt;br /&gt;While hacking with the gen_fsm above, I've found that there was no binary strings usage at all, &lt;br /&gt;manipulations are all done using lists... The binary type fit perfectly for this task so I've used&lt;br /&gt;it instead.&lt;br /&gt;&lt;br /&gt;So, I've rewrote everything, I've build a simple "smtp_proto.erl" file that just send binary strings for every state in the smtp protocol, and wait for a smtp response from the server. And write a simple "smtp_client.erl" that use only one single loop and call the needed "StateName" fun whenever it is needed.&lt;br /&gt;&lt;br /&gt;Here's the loop fun, where everything takes place:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(Phase,State) -&gt;&lt;br /&gt; receive &lt;br /&gt;  {'$c', Who, info} -&gt;&lt;br /&gt;   Who ! {ok, Phase, State#state.to},&lt;br /&gt;   loop(Phase, State); &lt;br /&gt;&lt;br /&gt;  {'$c', stop} -&gt;&lt;br /&gt;   error_logger:info_msg("Forced to quit~n"),&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;    exit({stop, Email});&lt;br /&gt;&lt;br /&gt;  {tcp, Socket, Data} -&gt;&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;   case smtp_proto:check(Data) of&lt;br /&gt;    {more, _Rest} -&gt;&lt;br /&gt;     inet:setopts(Socket, [{active,once}]),&lt;br /&gt;     loop(Phase, State);&lt;br /&gt;&lt;br /&gt;    {ok, _Rest} -&gt;&lt;br /&gt;      inet:setopts(Socket, [{active,once}]),&lt;br /&gt;     case ?MODULE:Phase({Socket, Data}, State) of&lt;br /&gt;&lt;br /&gt;      {ok, NewPhase, NewState} -&gt;&lt;br /&gt;       loop(NewPhase, NewState);&lt;br /&gt; &lt;br /&gt;      {stop, success, _NewState} -&gt;&lt;br /&gt;       mail_stats:add(success),&lt;br /&gt;       exit({ok, Email});&lt;br /&gt;&lt;br /&gt;      {stop, How, _NewState} -&gt;&lt;br /&gt;       exit({How, Email});&lt;br /&gt;&lt;br /&gt;      {error, _NewPhase, _NewState} -&gt;&lt;br /&gt;       error_logger:error_msg("Error: ~p '~p'~n", [Phase, Email]),&lt;br /&gt;       exit({error, Email});&lt;br /&gt;&lt;br /&gt;      _Any -&gt;&lt;br /&gt;       error_logger:msg("What: ~p~n", [_Any]),&lt;br /&gt;       exit({error, _Any})&lt;br /&gt;     end;&lt;br /&gt;&lt;br /&gt;    {error, 421, Rest} -&gt;&lt;br /&gt;     error_logger:error_msg("Error ~p for ~s (greylist): ~p ~p~n", [Phase, Email, 421, Rest]),&lt;br /&gt;     greylist(State),&lt;br /&gt;     exit({error, Rest});&lt;br /&gt;     &lt;br /&gt;    {error, Code, Rest} -&gt;&lt;br /&gt;     error_logger:error_msg("Error ~p for ~s: ~p ~p~n", [Phase, Email, Code, Rest]),&lt;br /&gt;     exit({error, Rest});&lt;br /&gt;&lt;br /&gt;    {error, Rest} -&gt;&lt;br /&gt;     error_logger:error_msg("Error ~p for ~s: ~p~n", [Phase, Email, Rest]),&lt;br /&gt;     exit({error, Rest}) &lt;br /&gt;   end;  &lt;br /&gt;&lt;br /&gt;  {tcp_error, _Socket, timeout} -&gt;&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;   error_logger:error_msg("Error: ~p 'timeout' for ~s~n", [Phase, Email]),&lt;br /&gt;   exit({error, timeout});&lt;br /&gt;   &lt;br /&gt;  {tcp_closed, _Socket} -&gt;&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;   error_logger:error_msg("Error: ~p 'connection closed' for ~s~n", [Phase, Email]),&lt;br /&gt;   exit({error, closed});&lt;br /&gt;&lt;br /&gt;  _Any -&gt;&lt;br /&gt;   error_logger:error_msg("Unhandled message: ~p~n", [_Any]),&lt;br /&gt;   loop(Phase,State) &lt;br /&gt;&lt;br /&gt; after ?INACTIVITY -&gt;&lt;br /&gt;  exit({error, inactivity})&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;What's important here, is that the gen_tcp sends various messages to the process, &lt;br /&gt;and those messages controls the state of the fsm. &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    {ok, _Rest} -&gt;&lt;br /&gt;      inet:setopts(Socket, [{active,once}]),&lt;br /&gt;     case ?MODULE:Phase({Socket, Data}, State) of&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;My module "smtp_client" calls a fun "Phase" which correspond to a SMTP state...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(Phase,State) -&gt;&lt;br /&gt;...&lt;br /&gt;    {ok, _Rest} -&gt;&lt;br /&gt;      inet:setopts(Socket, [{active,once}]),&lt;br /&gt;     case ?MODULE:Phase({Socket, Data}, State) of&lt;br /&gt;      {ok, NewPhase, NewState} -&gt;&lt;br /&gt;       loop(NewPhase, NewState);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Let's take a look at the helo fun:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;helo({Socket, _Data}, State) -&gt;&lt;br /&gt; smtp_proto:mailfrom(Socket, State#state.mailfrom),&lt;br /&gt; {ok, mailfrom, State}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;It's job is simply calling "smtp_proto:mailfrom" and returning the tuple "{ok, mailfrom, State}".&lt;br /&gt;Then in the main loop the consequence is:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;     case ?MODULE:Phase({Socket, Data}, State) of&lt;br /&gt;      {ok, NewPhase, NewState} -&gt;&lt;br /&gt;       loop(NewPhase, NewState);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;NewPhase = mailfrom, and NewState = State...&lt;br /&gt;&lt;br /&gt;Now the mailfrom fun use exactly the same method:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;mailfrom({Socket, _Data}, State) -&gt;&lt;br /&gt; [_,_,Email] = State#state.to,&lt;br /&gt;     smtp_proto:rcptto(Socket, Email),&lt;br /&gt; {ok, rcptto, State}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The fun extract the email, then call the "smtp_proto:rcptto" on the Socket with the argument Email.&lt;br /&gt;Technically, it just write this on the Socket:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;RCPTO TO:&amp;lt;Email&gt;\r\n&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then it returns the tuple holding the new Phase "rcptto" and the new State (which is the same unmodified)&lt;br /&gt;&lt;br /&gt;So what's nice with this method, is that every tcp related &lt;br /&gt;actions are handled within only one and only loop. None of Phase fun need&lt;br /&gt;to catch {tcp_ messages or handle tcp disconnections.&lt;br /&gt;&lt;br /&gt;The last thing before the full code, remember &lt;a href="http://easyerl.blogspot.com/2007/06/retrieve-mx-dns-record-using-erlang.html"&gt;the mx trick&lt;/a&gt; that returns a list of valid servers. &lt;br /&gt;Then you'll understand why the start_link fun takes a list of servers as first parameter...&lt;br /&gt;(if one mx is down, connect to the next one)&lt;br /&gt;&lt;br /&gt;Now the full source code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(smtp_client).&lt;br /&gt;&lt;br /&gt;-record(state,{&lt;br /&gt; socket,&lt;br /&gt; servername,&lt;br /&gt; mailfrom, to,&lt;br /&gt; data }).&lt;br /&gt;&lt;br /&gt;%% Extra long timeout for strange SMTPs...&lt;br /&gt;-define(TIMEOUT, 10000).&lt;br /&gt;-define(INACTIVITY, 100000).&lt;br /&gt;-define(GREYSLEEP, 300000).   %5 minutes&lt;br /&gt;&lt;br /&gt;% States&lt;br /&gt;-export([connect/2,helo/2,mailfrom/2,rcptto/2,data/2,quit/2]).&lt;br /&gt;&lt;br /&gt;%% External Exports&lt;br /&gt;-export([start_link/1,start_link/6,stop/1]).&lt;br /&gt;&lt;br /&gt;%% gen_server callbacks&lt;br /&gt;-export([init/6]).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%%% API&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;% Manual Start&lt;br /&gt;&lt;br /&gt;% Supervised Start&lt;br /&gt;start_link(Server,Port,ServerName,MailFrom,To,Data) -&gt;&lt;br /&gt; spawn_link(?MODULE, init, [Server,Port,ServerName,MailFrom,To,Data]).&lt;br /&gt;&lt;br /&gt;stop(Pid) -&gt;&lt;br /&gt; Pid ! {'$c', stop}.&lt;br /&gt; &lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%%% HELO State&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt; &lt;br /&gt;helo(timeout, State) -&gt;&lt;br /&gt; error_logger:error_msg("helo timeout (~p)~n", [State]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt;helo({timeout, _Ref, Reason}, State) -&gt;&lt;br /&gt; [_,_,Email] = State#state.to,&lt;br /&gt; error_logger:error_msg("Timeout: ~p email: '~s'~n", [Reason, Email]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;helo({Socket, _Data}, State) -&gt;&lt;br /&gt; smtp_proto:mailfrom(Socket, State#state.mailfrom),&lt;br /&gt; {ok, mailfrom, State}.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;%%&lt;br /&gt;%% Right after the connection... &lt;br /&gt;%%&lt;br /&gt;connect({Socket, _Data}, State) -&gt;&lt;br /&gt; smtp_proto:helo(Socket, State#state.servername),&lt;br /&gt; {ok, helo, State}.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%%% MAILFROM State&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt; &lt;br /&gt;mailfrom(timeout, State) -&gt;&lt;br /&gt; error_logger:error_msg("timeout (~p)~n", [State]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt;mailfrom({timeout, _Ref, Reason}, State) -&gt;&lt;br /&gt; [_,_,Email] = State#state.to,&lt;br /&gt; error_logger:error_msg("Timeout: ~p email: '~s'~n", [Reason, Email]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt;mailfrom({Socket, _Data}, State) -&gt;&lt;br /&gt; [_,_,Email] = State#state.to,&lt;br /&gt;     smtp_proto:rcptto(Socket, Email),&lt;br /&gt; {ok, rcptto, State}.&lt;br /&gt;&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%%% RCPT TO State&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;rcptto(timeout, State) -&gt;&lt;br /&gt; error_logger:error_msg("rcptto timeout (~p)~n", [State]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt;rcptto({timeout, _Ref, Reason}, State) -&gt;&lt;br /&gt; [_,_,Email] = State#state.to,&lt;br /&gt; error_logger:error_msg("Timeout: ~p email: '~s'~n", [Reason, Email]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt;rcptto({Socket, _Data}, State) -&gt;&lt;br /&gt;       smtp_proto:data(Socket),&lt;br /&gt;   {ok, data, State}.&lt;br /&gt;&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%%% DATA State&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;data(timeout, State) -&gt;&lt;br /&gt; error_logger:error_msg("data timeout (~p)~n", [State]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt;data({timeout, _Ref, Reason}, State) -&gt;&lt;br /&gt; [_,_,Email] = State#state.to,&lt;br /&gt; error_logger:error_msg("Timeout: ~p email: '~s'~n", [Reason, Email]),&lt;br /&gt;  {ok, data, State};&lt;br /&gt;&lt;br /&gt;data({Socket, _Data}, State) -&gt;&lt;br /&gt; smtp_proto:write(Socket, [ State#state.data, &lt;&lt;"\r\n."&gt;&gt; ]),&lt;br /&gt; {ok, quit, State}.&lt;br /&gt; &lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%%% QUIT State&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;quit(timeout, State) -&gt;&lt;br /&gt; error_logger:error_msg("data timeout (~p)~n", [State]),&lt;br /&gt;  {stop, normal, State};&lt;br /&gt;&lt;br /&gt;quit({_Socket, _Data}, State) -&gt;&lt;br /&gt;  [_,_,Email] = State#state.to,&lt;br /&gt; smtp_proto:quit(State#state.socket),&lt;br /&gt;  error_logger:info_msg("Sent to ~s : OK~n", [Email]),&lt;br /&gt;  {stop, success, State}.&lt;br /&gt;&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%%% Callback functions from gen_fsm&lt;br /&gt;%%%----------------------------------------------------------------------&lt;br /&gt;%% Timeout from ?TIME&lt;br /&gt;%% gen_fsm:start_timer(70000, slow),&lt;br /&gt;&lt;br /&gt;init(Servers,Port,ServerName,MailFrom,To,Data) -&gt;&lt;br /&gt; process_flag(trap_exit, true),&lt;br /&gt; connect(Servers,Port,ServerName,MailFrom,To,Data).&lt;br /&gt;&lt;br /&gt;connect([], _,_,_,To,_) -&gt;&lt;br /&gt; [_,_,Email] = To,&lt;br /&gt; exit({error, Email});&lt;br /&gt;&lt;br /&gt;connect([H | T],Port,ServerName,MailFrom,To,Data) -&gt;&lt;br /&gt; case gen_tcp:connect(H,Port,[binary,{packet,0},{active,once}], ?TIMEOUT) of &lt;br /&gt;  {ok,Socket} -&gt;&lt;br /&gt;   inet:setopts(Socket, [{active,once}]),&lt;br /&gt;   loop(connect, #state{ &lt;br /&gt;     socket=Socket,&lt;br /&gt;     servername=ServerName,&lt;br /&gt;     to=To,&lt;br /&gt;     mailfrom=MailFrom,&lt;br /&gt;     data=Data});&lt;br /&gt;  {error, timeout} -&gt;&lt;br /&gt;   connect(T,Port,ServerName,MailFrom,To,Data);&lt;br /&gt;&lt;br /&gt;  {error, Reason} -&gt;&lt;br /&gt;   exit({error, Reason})&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;loop(Phase,State) -&gt;&lt;br /&gt; receive &lt;br /&gt;  {'$c', Who, info} -&gt;&lt;br /&gt;   Who ! {ok, Phase, State#state.to},&lt;br /&gt;   loop(Phase, State); &lt;br /&gt;&lt;br /&gt;  {'$c', stop} -&gt;&lt;br /&gt;   error_logger:info_msg("Forced to quit~n"),&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;    exit({stop, Email});&lt;br /&gt;&lt;br /&gt;  {tcp, Socket, Data} -&gt;&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;   case smtp_proto:check(Data) of&lt;br /&gt;    {more, _Rest} -&gt;&lt;br /&gt;     inet:setopts(Socket, [{active,once}]),&lt;br /&gt;     loop(Phase, State);&lt;br /&gt;&lt;br /&gt;    {ok, _Rest} -&gt;&lt;br /&gt;      inet:setopts(Socket, [{active,once}]),&lt;br /&gt;     case ?MODULE:Phase({Socket, Data}, State) of&lt;br /&gt;&lt;br /&gt;      {ok, NewPhase, NewState} -&gt;&lt;br /&gt;       loop(NewPhase, NewState);&lt;br /&gt; &lt;br /&gt;      {stop, success, _NewState} -&gt;&lt;br /&gt;       mail_stats:add(success),&lt;br /&gt;       exit({ok, Email});&lt;br /&gt;&lt;br /&gt;      {stop, How, _NewState} -&gt;&lt;br /&gt;       exit({How, Email});&lt;br /&gt;&lt;br /&gt;      {error, _NewPhase, _NewState} -&gt;&lt;br /&gt;       error_logger:error_msg("Error: ~p '~p'~n", [Phase, Email]),&lt;br /&gt;       exit({error, Email});&lt;br /&gt;&lt;br /&gt;      _Any -&gt;&lt;br /&gt;       error_logger:msg("What: ~p~n", [_Any]),&lt;br /&gt;       exit({error, _Any})&lt;br /&gt;     end;&lt;br /&gt;&lt;br /&gt;    {error, 421, Rest} -&gt;&lt;br /&gt;     error_logger:error_msg("Error ~p for ~s (greylist): ~p ~p~n", [Phase, Email, 421, Rest]),&lt;br /&gt;     greylist(State),&lt;br /&gt;     exit({error, Rest});&lt;br /&gt;     &lt;br /&gt;    {error, Code, Rest} -&gt;&lt;br /&gt;     error_logger:error_msg("Error ~p for ~s: ~p ~p~n", [Phase, Email, Code, Rest]),&lt;br /&gt;     exit({error, Rest});&lt;br /&gt;&lt;br /&gt;    {error, Rest} -&gt;&lt;br /&gt;     error_logger:error_msg("Error ~p for ~s: ~p~n", [Phase, Email, Rest]),&lt;br /&gt;     exit({error, Rest}) &lt;br /&gt;   end;  &lt;br /&gt;&lt;br /&gt;  {tcp_error, _Socket, timeout} -&gt;&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;   error_logger:error_msg("Error: ~p 'timeout' for ~s~n", [Phase, Email]),&lt;br /&gt;   exit({error, timeout});&lt;br /&gt;   &lt;br /&gt;  {tcp_closed, _Socket} -&gt;&lt;br /&gt;   [_,_, Email ] = State#state.to,&lt;br /&gt;   error_logger:error_msg("Error: ~p 'connection closed' for ~s~n", [Phase, Email]),&lt;br /&gt;   exit({error, closed});&lt;br /&gt;&lt;br /&gt;  _Any -&gt;&lt;br /&gt;   error_logger:error_msg("Unhandled message: ~p~n", [_Any]),&lt;br /&gt;   loop(Phase,State) &lt;br /&gt;&lt;br /&gt; after ?INACTIVITY -&gt;&lt;br /&gt;  exit({error, inactivity})&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;greylist(State) -&gt;&lt;br /&gt; spawn(greylist, start, [ ?GREYSLEEP, State#state.servername,&lt;br /&gt;  State#state.mailfrom,State#state.to,State#state.data]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's the smtp_proto module:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(smtp_proto).&lt;br /&gt;-export([read/1, &lt;br /&gt; read/2, &lt;br /&gt; write/2,&lt;br /&gt; helo/2,&lt;br /&gt; ehlo/2,&lt;br /&gt; mailfrom/2,&lt;br /&gt; rcptto/2,&lt;br /&gt; data/1,&lt;br /&gt; noop/1,&lt;br /&gt; rset/1,&lt;br /&gt; help/1,&lt;br /&gt; check/1,&lt;br /&gt; quit/1]).&lt;br /&gt;&lt;br /&gt;read(Socket) -&gt;&lt;br /&gt; read(Socket, 5000).&lt;br /&gt;&lt;br /&gt;read(Socket, Timeout) -&gt;&lt;br /&gt; case gen_tcp:recv(Socket, 0, Timeout) of&lt;br /&gt;  {ok, Packet} -&gt;&lt;br /&gt;   check(Packet);&lt;br /&gt;&lt;br /&gt;  {error, Why} -&gt;&lt;br /&gt;   {error, Why} &lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;%% 2XX codes are OK&lt;br /&gt;check(&lt;&lt;"250 ", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {ok, Rest};&lt;br /&gt;check(&lt;&lt;"214", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {ok, Rest};&lt;br /&gt;check(&lt;&lt;"220", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {ok, Rest};&lt;br /&gt;check(&lt;&lt;"221", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {ok, Rest};&lt;br /&gt;check(&lt;&lt;"354", _Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {ok, data};&lt;br /&gt;&lt;br /&gt;%Errors&lt;br /&gt;check(&lt;&lt;"421", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {error, 421, Rest};&lt;br /&gt;check(&lt;&lt;"503", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {error, 503, Rest};&lt;br /&gt;check(&lt;&lt;"511", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {error, 511, Rest};&lt;br /&gt;check(&lt;&lt;"540", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {error, 540, Rest};&lt;br /&gt;check(&lt;&lt;"550", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {error, 550, Rest};&lt;br /&gt;check(&lt;&lt;"554", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {error, 554, Rest};&lt;br /&gt;check(Bin) -&gt;&lt;br /&gt; {more, Bin}.&lt;br /&gt;&lt;br /&gt;write(Socket, Msg) -&gt;&lt;br /&gt; gen_tcp:send(Socket, [Msg, &lt;&lt;"\r\n"&gt;&gt;] ).&lt;br /&gt;&lt;br /&gt;helo(Socket, Name) -&gt;&lt;br /&gt; Msg = [ &lt;&lt;"HELO "&gt;&gt;, Name ],&lt;br /&gt; write(Socket, iolist_to_binary(Msg)).&lt;br /&gt;&lt;br /&gt;ehlo(Socket, Name) -&gt;&lt;br /&gt; Msg = [ &lt;&lt;"EHLO "&gt;&gt;, list_to_binary(Name) ],&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;br /&gt;rcptto(Socket, Name) -&gt;&lt;br /&gt; Msg = iolist_to_binary([ &lt;&lt;"RCPT TO:&lt;"&gt;&gt;, Name, &lt;&lt;"&gt;"&gt;&gt; ]),&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;br /&gt;mailfrom(Socket, Name) -&gt;&lt;br /&gt; Msg = [ &lt;&lt;"MAIL FROM:&lt;"&gt;&gt;, list_to_binary(Name), &lt;&lt;"&gt;"&gt;&gt; ],&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;br /&gt;help(Socket) -&gt;&lt;br /&gt; Msg = &lt;&lt;"HELP"&gt;&gt;,&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;br /&gt;noop(Socket) -&gt;&lt;br /&gt; Msg = &lt;&lt;"NOOP"&gt;&gt;,&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;br /&gt;quit(Socket) -&gt;&lt;br /&gt; Msg = &lt;&lt;"QUIT"&gt;&gt;,&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;br /&gt;rset(Socket) -&gt;&lt;br /&gt; Msg = &lt;&lt;"RSET"&gt;&gt;,&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;br /&gt;data(Socket) -&gt;&lt;br /&gt; Msg = &lt;&lt;"DATA"&gt;&gt;,&lt;br /&gt; write(Socket, Msg).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;But wait ! There's more !&lt;br /&gt;BONUS: the greylist client &lt;br /&gt;(where you can see the smtp_client in action):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(greylist).&lt;br /&gt;&lt;br /&gt;-export([start/5, stop/1, flush/1]).&lt;br /&gt;-define(TIMEOUT, 100000).&lt;br /&gt;&lt;br /&gt;start(Sleep,ServerName,MailFrom,To,Data) -&gt;&lt;br /&gt;        receive &lt;br /&gt;                {'$c', flush} -&gt;&lt;br /&gt;                        ok;&lt;br /&gt;&lt;br /&gt;                {'$c', stop} -&gt;&lt;br /&gt;                        error_logger:info_msg("~p: Manual stop~n", [?MODULE]),&lt;br /&gt;                        exit(normal)&lt;br /&gt;        after Sleep -&gt; &lt;br /&gt;                ok &lt;br /&gt;        end,&lt;br /&gt;        [ _, _, Email ] = To,&lt;br /&gt;        [ _, Domain ] = string:tokens( Email, "@"),&lt;br /&gt;        List = mmailer:get_mx(Domain),&lt;br /&gt;        {_, Servers} = lists:unzip( lists:keysort(1, List) ),&lt;br /&gt;        Pid = smtp_client:start_link(Servers, 25, ServerName,MailFrom,To,Data),&lt;br /&gt;        loop(Pid).&lt;br /&gt;&lt;br /&gt;flush(Pid) -&gt;&lt;br /&gt;        Pid ! {'$c', flush}.&lt;br /&gt;&lt;br /&gt;stop(Pid) -&gt;&lt;br /&gt;        Pid ! {'$c', stop}.&lt;br /&gt;&lt;br /&gt;loop(Child) -&gt;&lt;br /&gt;        receive &lt;br /&gt;                {'$c', stop} -&gt;&lt;br /&gt;                        smtp_client:stop(Child),&lt;br /&gt;                        error_logger:info_msg("Greylist: Manual stop~n");&lt;br /&gt;&lt;br /&gt;                {'EXIT', Child, Reason} -&gt;&lt;br /&gt;                        error_logger:info_msg("~p child ~p died : ~p~n", [?MODULE, Child, Reason]);&lt;br /&gt;                &lt;br /&gt;                {'EXIT', Pid, Reason} -&gt;&lt;br /&gt;                        error_logger:info_msg("~p not own child ~p died : ~p~n", [?MODULE, Pid, Reason]),&lt;br /&gt;                        loop(Child);&lt;br /&gt;&lt;br /&gt;                Msg -&gt; &lt;br /&gt;                        error_logger:error_msg("~p: Unhandled message received: '~p'", [?MODULE, Msg]),&lt;br /&gt;                        loop(Child) &lt;br /&gt;&lt;br /&gt;        after ?TIMEOUT -&gt;&lt;br /&gt;                smtp_client:stop(Child)&lt;br /&gt;        end.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3742344622512441331?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3742344622512441331/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3742344622512441331' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3742344622512441331'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3742344622512441331'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/09/genfsm-vs-simple-fsm-for-sending-emails.html' title='gen_fsm vs a simple fsm for sending emails...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1944999828544281660</id><published>2008-07-15T10:42:00.003+02:00</published><updated>2008-07-15T11:03:52.573+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='picasa'/><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='googerl'/><title type='text'>From Googerl to googleatom_app</title><content type='html'>Hi, (a quick post)&lt;br /&gt;I've been busy this weekend working on a more generic layer for google API. Here comes the '&lt;b&gt;googleatom_app&lt;/b&gt;'.&lt;br /&gt;&lt;br /&gt;I've uploaded it in the &lt;a href="http://code.google.com/p/googerl/source/browse"&gt;googerl&lt;/a&gt; project.&lt;br /&gt;The application needs 'crypto, ssl, inets' to work. &lt;pre&gt;[ application:start(X) || X &lt;- [crypto, ssl, inets, googleatom] ]&lt;/pre&gt; should start the application.&lt;br /&gt;There's currently not much documentation, this is still at a very early stage. Here's a list of what you can do now:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Post an article into blogger&lt;/li&gt;&lt;li&gt;Post a photo into picasa, taken from a file or from a URL (inets download the image and send it to picasa)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Once the application has started, you'll see two new registered processes 'blogger_srv' and 'picasa_srv'. The first thing you need to do before being able to use thoses process is setting your google credentials:&lt;pre&gt;picasa_srv:auth(Username, Password).&lt;/pre&gt;Once the call returns you'll receive a google token string. If not you'll have to investigate a little :). &lt;br /&gt;Ok now that you're authenticated you can post a nice photo into your gallery: &lt;pre&gt;picasa_srv:new(Username, Album, "http://www.aniceimageservier.com/superimage.jpg", "Fake Image Name").&lt;/pre&gt;&lt;br /&gt;Since the process of posting images takes time, picasa_srv uses 'gen_server:cast', and now the success is indicated by some 'error_logger:info_msg' ... (xmerl is used to extract various new urls of your posted photo).&lt;br /&gt;&lt;br /&gt;That's it for this really quick introduction, I'll will update files within days, actually some funs are not available everywhere, I'll fix this. &lt;br /&gt;&lt;br /&gt;I'll post a googleatom_app tutorial later this week...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1944999828544281660?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1944999828544281660/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1944999828544281660' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1944999828544281660'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1944999828544281660'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/07/from-googerl-to-googleatomapp.html' title='From Googerl to googleatom_app'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6563934324215118647</id><published>2008-07-10T08:57:00.006+02:00</published><updated>2008-07-10T13:04:19.558+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='http'/><category scheme='http://www.blogger.com/atom/ns#' term='mobile'/><category scheme='http://www.blogger.com/atom/ns#' term='webservice'/><category scheme='http://www.blogger.com/atom/ns#' term='mochiweb'/><title type='text'>My mochiweb webservice...</title><content type='html'>Now that &lt;a href="http://code.google.com/p/mochiweb/"&gt;mochiweb&lt;/a&gt; is popular, and that I use it for some of my work, I can publish here some webservice thingy that I use.&lt;br /&gt;&lt;br /&gt;I wanted to have this simple thing: modulename,funname, and parameters inside the URL:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;http://www.server.tld/modulename/fun/arg1/arg2 &lt;br /&gt;wmodulename:Fun(arg1, arg2)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;(Note the 'w' prefix, I explain it later)&lt;br /&gt;&lt;br /&gt;Here's the code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(webservice).&lt;br /&gt;-export([start/0, start/2, loop/2, stop/0, test/0]).&lt;br /&gt;-export([dolog/2]).&lt;br /&gt;-define(PORT, 8080).&lt;br /&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt; start("/home/rolphin/Devel/Web", ?PORT).&lt;br /&gt;&lt;br /&gt;start(Wwwroot, Port) -&gt;&lt;br /&gt; Loop = fun (Req) -&gt;&lt;br /&gt;  ?MODULE:loop(Req, Wwwroot)&lt;br /&gt; end,&lt;br /&gt; mochiweb_http:start([{loop, Loop}, {name, ?MODULE}, {port, Port}]).&lt;br /&gt;&lt;br /&gt;stop() -&gt;&lt;br /&gt;    mochiweb_http:stop(?MODULE).&lt;br /&gt;&lt;br /&gt;loop(Req, DocRoot) -&gt;&lt;br /&gt; log(Req),&lt;br /&gt; case string:tokens(Req:get(path), "/") of&lt;br /&gt;  [ "dump" ] -&gt;&lt;br /&gt;   Req:ok({"text/plain",&lt;br /&gt;    io_lib:format("~p~n", [Req:dump()])});&lt;br /&gt;&lt;br /&gt;  [ "favicon.ico" ] -&gt;&lt;br /&gt;   Req:respond({404, [], ""});&lt;br /&gt;&lt;br /&gt;  [ "codepath" ] -&gt;&lt;br /&gt;   Req:ok({"text/plain",&lt;br /&gt;    io_lib:format("codepath: ~p~n", [code:get_path()])});&lt;br /&gt;&lt;br /&gt;  [ "codepath", "json" ] -&gt;&lt;br /&gt;   Req:ok({"text/plain",&lt;br /&gt;    mochijson:encode({array, code:get_path()})});&lt;br /&gt;&lt;br /&gt;  [ Path, Fun | Elems ] -&gt;&lt;br /&gt;   % Every module name should begin with 'w'&lt;br /&gt;   dispatch(Req, DocRoot, list_to_atom("w" ++ Path), Fun, Elems);&lt;br /&gt;&lt;br /&gt;  [] -&gt;&lt;br /&gt;   launch(Req, DocRoot, wdefault, do, []);&lt;br /&gt;&lt;br /&gt;  _ -&gt;&lt;br /&gt;   Req:respond({502, [], []})&lt;br /&gt;   &lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;dispatch(Req, DocRoot, Module, Fun, Elems) -&gt;&lt;br /&gt; M = Req:get(method),&lt;br /&gt; case M of&lt;br /&gt;  'GET' -&gt;&lt;br /&gt;   launch(Req, DocRoot, Module, Fun, Elems);&lt;br /&gt;  'POST' -&gt;&lt;br /&gt;   launch(Req, DocRoot, Module, Fun, Elems);&lt;br /&gt;  'PUT' -&gt;&lt;br /&gt;   launch(Req, DocRoot, Module, Fun, Elems);&lt;br /&gt;  'DELETE' -&gt;&lt;br /&gt;   launch(Req, DocRoot, Module, Fun, Elems);&lt;br /&gt;  'HEAD' -&gt;&lt;br /&gt;   launch(Req, DocRoot, Module, Fun, Elems);&lt;br /&gt;  _Any -&gt;&lt;br /&gt;   launch(Req, DocRoot, wdefault, get, [])&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;launch(Req, DocRoot, wcontent, Fun, Args) -&gt;&lt;br /&gt; case catch wcontent:default(Req, DocRoot, [ Fun | Args] ) of&lt;br /&gt;  {'EXIT', {Type, _Error}} -&gt;&lt;br /&gt;   Req:ok({"text/plain",  &lt;br /&gt;    io_lib:format("GET Error: '~p' for '~p' ~p ~p~n~p~n", [Type, wcontent, Fun, Args, _Error])});&lt;br /&gt;  _ -&gt;&lt;br /&gt;   ok&lt;br /&gt; end;&lt;br /&gt; &lt;br /&gt;launch(Req, DocRoot, Module, Fun, Args) -&gt;&lt;br /&gt; F = list_to_atom(Fun),&lt;br /&gt; case catch Module:F(Req, DocRoot, Args) of&lt;br /&gt;  {'EXIT', {Type, _Error}} -&gt;&lt;br /&gt;   Req:ok({"text/plain",  &lt;br /&gt;    io_lib:format("~p Error: '~p' for ~p ~p ~p~n~p~n", [Req:get(method), Type, Module, Fun, Args, _Error])});&lt;br /&gt;  _ -&gt;&lt;br /&gt;   ok&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;log(Req) -&gt;&lt;br /&gt; Ip = Req:get(peer),&lt;br /&gt; spawn(?MODULE, dolog, [Req, Ip]).&lt;br /&gt;&lt;br /&gt;dolog(Req, Ip) -&gt;&lt;br /&gt; stat_logger:log("~p ~p", [Ip, Req:get(path)]).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;First you can see that I use "mochiweb:start" and "mochiweb:stop" in the "start" and "stop" funs.&lt;br /&gt;Then parameters are extracted from the URI:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; case string:tokens(Req:get(path), "/") of&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I split the URI on the "/" character, and compare the resulting list with various possibilities.&lt;br /&gt;&lt;br /&gt;Then the main part of the code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;launch(Req, DocRoot, Module, Fun, Args) -&gt;&lt;br /&gt; F = list_to_atom(Fun),&lt;br /&gt; case catch Module:F(Req, DocRoot, Args) of&lt;br /&gt;  {'EXIT', {Type, _Error}} -&gt;&lt;br /&gt;   Req:ok({"text/plain",  &lt;br /&gt;    io_lib:format("~p Error: '~p' for ~p ~p ~p~n~p~n", [Req:get(method), Type, Module, Fun, Args, _Error])});&lt;br /&gt;  _ -&gt;&lt;br /&gt;   ok&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Using "catch" prevent your app from crashing without any information. If a module doesn't exists, you'll be able to see why (the main problem is sometimes the module is not in the code:path...) &lt;br /&gt;&lt;br /&gt;Every webservice module name should start with "w", it's some sort of namespace :), now you know the reason for the 'w' prefix:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  [ Path, Fun | Elems ] -&gt;&lt;br /&gt;   % Every module name should begin with 'w'&lt;br /&gt;   dispatch(Req, DocRoot, list_to_atom("w" ++ Path), Fun, Elems);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now, how can I use it ? It's simple, you just have to build a module with such a template:&lt;pre&gt;&lt;br /&gt;-module(wsample).&lt;br /&gt;-export([do/3]).&lt;br /&gt;&lt;br /&gt;do(Req, _DocRoot, Args) -&gt;&lt;br /&gt; Req:ok({"text/plain", [ &lt;&lt;"Hello "&gt;&gt;, hd(Args) , &lt;&lt;", you are authorized :)"&gt;&gt;] }).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then you build it, and store it somewhere in your "code:path". And call&lt;pre&gt;&lt;br /&gt;webservice:start().&lt;br /&gt;&lt;/pre&gt; in your erl shell. Then locate your webrowser to the URI &lt;pre&gt;www.server.tld:8080/sample/do/PUT-ANYTHING-YOU-WANT-HERE&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;As a sample module, here is a module that is used everyday the "mobile tags" generator:&lt;pre&gt;&lt;br /&gt;-module(wbarcode).&lt;br /&gt;-export([do/3]).&lt;br /&gt;&lt;br /&gt;-define(PNG, "image/png").&lt;br /&gt;&lt;br /&gt;do(Req, _DocRoot, _Args) -&gt;&lt;br /&gt; barcode(Req).&lt;br /&gt;&lt;br /&gt;% the process 'barcode' must be there&lt;br /&gt;barcode(Req) -&gt;&lt;br /&gt; Options = Req:parse_qs(),&lt;br /&gt; %io:format("~p~n", [Options]),&lt;br /&gt;&lt;br /&gt; Text = proplists:get_value("text", Options, "http://www.shootit.fr"),&lt;br /&gt; Res = proplists:get_value("res", Options, "72"),&lt;br /&gt; Z = proplists:get_value("z", Options, "1"),&lt;br /&gt;&lt;br /&gt; case barcode:generate('barcode', Text) of &lt;br /&gt;  timeout -&gt;&lt;br /&gt;   Req:ok({"text/plain", [], &lt;&lt;"Timeout"&gt;&gt;});&lt;br /&gt;  &lt;br /&gt;  {data, {Width, Height}, Data} -&gt;&lt;br /&gt;   Zoom = list_to_integer(Z),&lt;br /&gt;   Xres = integer_to_list(list_to_integer(Res)  * Zoom),&lt;br /&gt;   Yres = integer_to_list(list_to_integer(Res)  * Zoom),&lt;br /&gt;   Params = [ {"PS", [integer_to_list(Width + 1), integer_to_list(Height + 1)]}, {"HW", [Xres, Yres]} ],&lt;br /&gt;   case gs:draw(gs, Data, Params) of&lt;br /&gt;    {png, Image} -&gt;&lt;br /&gt;     Req:ok({?PNG, Image});&lt;br /&gt;&lt;br /&gt;    _E -&gt;&lt;br /&gt;     Req:ok({"text/plain", [], [ &lt;&lt;"Error: "&gt;&gt;, &lt;br /&gt;      io_lib:format("~s", [_E])]})&lt;br /&gt;   end&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6563934324215118647?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6563934324215118647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6563934324215118647' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6563934324215118647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6563934324215118647'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/07/my-mochiweb-webservice.html' title='My mochiweb webservice...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-4064140659788420661</id><published>2008-07-09T16:25:00.004+02:00</published><updated>2008-07-10T08:57:44.222+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='paging'/><title type='text'>Dealing with multiple pages of results easily</title><content type='html'>We you want to deal with multiples pages of responses and using MySQL, you are always dealing with some sort of "LIMIT" statement. Since erlang is a vm, you can efficiently retrieving results while the user is not asking you to... You can transparently in the background prefetch data for him. This is what I call a pager.&lt;br /&gt;&lt;br /&gt;I've build simple module to that for me :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(db_pager).&lt;br /&gt;-export([retrieve/1, loop/2]).&lt;br /&gt;&lt;br /&gt;new(Request, Start, Step) -&gt;&lt;br /&gt; Prepared = &lt;br /&gt; Pid = spawn(?MODULE, loop, [Start, Step]),&lt;br /&gt; retrieve(Pid),&lt;br /&gt;&lt;br /&gt;retrieve(Pid) -&gt;&lt;br /&gt; io:format("DEBUG: ~p ! {next, ~p}~n", [ Pid, self() ]),&lt;br /&gt; Pid ! { next, self() },&lt;br /&gt; receive&lt;br /&gt;  {eof, Msg} -&gt;&lt;br /&gt;   io:format("eof: ~p~n", [Msg]),&lt;br /&gt;   {eof, Msg};&lt;br /&gt;&lt;br /&gt;  {data, Data} -&gt;&lt;br /&gt;   io:format("Received: ~p~n", [Data]),&lt;br /&gt;   timer:sleep(3000),&lt;br /&gt;   retrieve(Pid); &lt;br /&gt;&lt;br /&gt;  stop -&gt;&lt;br /&gt;   stop;&lt;br /&gt;&lt;br /&gt;  _E -&gt;&lt;br /&gt;   io:format("Unhandled: ~p~n", [_E]),&lt;br /&gt;   client(Pid) &lt;br /&gt; after 2000 -&gt;&lt;br /&gt;  timeout&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;select(Fields, Table) -&gt;&lt;br /&gt; [ Head | Rest ] = Fields,&lt;br /&gt; [ &lt;&lt;"select "&gt;&gt;, atom_to_list(Head),&lt;br /&gt;  lists:foldl(fun(X, Acc) -&gt;&lt;br /&gt;   [ [ &lt;&lt;","&gt;&gt; |  atom_to_list(X) ] | Acc ]&lt;br /&gt;   end, [], Rest),&lt;br /&gt;  &lt;&lt;" from "&gt;&gt;, atom_to_list(Table) ].&lt;br /&gt;&lt;br /&gt;sql(Current, Step) -&gt;&lt;br /&gt; Query = select([id,lastname,firstname], users),&lt;br /&gt; Next = Current + Step,&lt;br /&gt; case mysql:fetch(mysql, [ &lt;br /&gt;  Query, &lt;&lt;" where id between "&gt;&gt;, &lt;br /&gt;  integer_to_list(Current), &lt;&lt;" and "&gt;&gt;, integer_to_list(Next) ]) of&lt;br /&gt;&lt;br /&gt;  {data, Data} -&gt; &lt;br /&gt;   case mysql:get_result_rows(Data) of&lt;br /&gt;    [] -&gt;&lt;br /&gt;     {eof, empty};&lt;br /&gt;&lt;br /&gt;    Res -&gt;&lt;br /&gt;     %io:format("DEBUG: sql: data: ~p~n", [Data]),&lt;br /&gt;     {data, Res}  &lt;br /&gt;   end;&lt;br /&gt;&lt;br /&gt;  {error, Data} -&gt; &lt;br /&gt;   io:format("DEBUG: sql: data: ~p~n", [Data]),&lt;br /&gt;   {eof, mysql:get_result_reason(Data)}&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;loop(Current, Step) -&gt;&lt;br /&gt; loop(Current, Step, []).&lt;br /&gt;&lt;br /&gt;loop(Current, Step, []) -&gt;&lt;br /&gt; Res = sql(Current, Step),&lt;br /&gt; loop(Current + Step, Step, Res);&lt;br /&gt;&lt;br /&gt;loop(Current, Step, Res) -&gt;&lt;br /&gt; Next = Current + Step,&lt;br /&gt; receive&lt;br /&gt;  {next, Who} -&gt;&lt;br /&gt;   Who ! Res,&lt;br /&gt;   NewRes = sql(Current, Step),&lt;br /&gt;   loop(Next, Step, NewRes);&lt;br /&gt;&lt;br /&gt;  stop -&gt;&lt;br /&gt;   stop&lt;br /&gt;&lt;br /&gt; after 30000 -&gt;&lt;br /&gt;  timeout&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This code build a sql query that contains some "BETWEEN" statement for some "id" field:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; Query = select([id,lastname,firstname], users),&lt;br /&gt; Next = Current + Step,&lt;br /&gt; case mysql:fetch(mysql, [ &lt;br /&gt;  Query, &lt;&lt;" where id between "&gt;&gt;, &lt;br /&gt;  integer_to_list(Current), &lt;&lt;" and "&gt;&gt;, integer_to_list(Next) ]) of&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This part specifically builds a string like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select id,lastname,firstname where id between X and Y&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Where X and Y are variable, X stands for the Current offset and Y the Next offset.&lt;br /&gt;&lt;br /&gt;When the code is called multiple times the X and Y are automatically computed, so every row from the table will be returned one page (Y - X elems) at a time. The "loop/1" function is here for that:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(Current, Step, Res) -&gt;&lt;br /&gt; Next = Current + Step,&lt;br /&gt; receive&lt;br /&gt;  {next, Who} -&gt;&lt;br /&gt;   Who ! Res,&lt;br /&gt;   NewRes = sql(Current, Step),&lt;br /&gt;   loop(Next, Step, NewRes);&lt;br /&gt;&lt;br /&gt;  stop -&gt;&lt;br /&gt;   stop&lt;br /&gt;&lt;br /&gt; after 30000 -&gt;&lt;br /&gt;  timeout&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The receiver Who will have informations one page at a time. The data sent to him is fetched before he needs to, except the first time:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(Current, Step, []) -&gt;&lt;br /&gt; Res = sql(Current, Step),&lt;br /&gt; loop(Current + Step, Step, Res);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-4064140659788420661?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/4064140659788420661/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=4064140659788420661' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4064140659788420661'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4064140659788420661'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/07/dealing-with-multiple-pages-of-results.html' title='Dealing with multiple pages of results easily'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2665464757293330209</id><published>2008-07-09T11:44:00.005+02:00</published><updated>2008-07-10T16:31:46.218+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sar'/><category scheme='http://www.blogger.com/atom/ns#' term='webservice'/><category scheme='http://www.blogger.com/atom/ns#' term='port'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='monitoring'/><category scheme='http://www.blogger.com/atom/ns#' term='sysstat'/><title type='text'>Monitoring your servers with sysstat (sar)</title><content type='html'>There's sometimes things that are so helpfull that you think that everyone is aware of them, but sometimes this is not the case. Here I'll talk about a little package that is so powerful and efficient that you won't change anymore...&lt;br /&gt;Taken from the ubuntu man page:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;DESCRIPTION&lt;br /&gt;       The sar command writes to standard output the contents of  selected  cumula-&lt;br /&gt;       tive activity counters in the operating system. The accounting system, based&lt;br /&gt;       on the values in the count and interval parameters, writes  information  the&lt;br /&gt;       specified  number of times spaced at the specified intervals in seconds.  If&lt;br /&gt;       the interval parameter is set to zero, the sar command displays the  average&lt;br /&gt;       statistics  for the time since the system was started. The default value for&lt;br /&gt;       the count parameter is 1. If its value is set to zero, then reports are gen-&lt;br /&gt;       erated continuously.  The collected data can also be saved in the file spec-&lt;br /&gt;       ified by the -o filename flag, in  addition  to  being  displayed  onto  the&lt;br /&gt;       screen.  If filename is omitted, sar uses the standard system activity daily&lt;br /&gt;       data file, the /var/log/sysstat/sadd file, where the dd parameter  indicates&lt;br /&gt;       the  current  day.   By  default  all the data available from the kernel are&lt;br /&gt;       saved in the data file. Exceptions are interrupts and disks data, for  which&lt;br /&gt;       the  relevant  options  must  be explicitly passed to sar (or to its backend&lt;br /&gt;       sadc ) when the data file is created (see options below).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;"sar" comes with the sysstat package. Once it's installed you can monitor your server like never before...&lt;br /&gt;&lt;br /&gt;Here's the description of the sysstat package from the &lt;a href="http://pagesperso-orange.fr/sebastien.godard/"&gt;author&lt;/a&gt;&lt;pre&gt;The sysstat utilities are a collection of performance monitoring tools for Linux. &lt;br /&gt;These include sar, sadf, mpstat, iostat, pidstat and sa tools. Go to the Features page to display &lt;br /&gt;a list of sysstat's features, or see the Documentation page to learn some more about them.&lt;/pre&gt;&lt;br /&gt;For example, you can watch realtime the network usage:&lt;pre&gt;&lt;br /&gt;# sar -n DEV 1 0&lt;br /&gt;Linux 2.6.22-15-generic (xXxXx)  07/09/2008&lt;br /&gt;&lt;br /&gt;11:26:36 AM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s&lt;br /&gt;11:26:37 AM        lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00&lt;br /&gt;11:26:37 AM      eth0      5.05      0.00      0.86      0.00      0.00      0.00      0.00&lt;br /&gt;&lt;br /&gt;11:26:37 AM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s&lt;br /&gt;11:26:38 AM        lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00&lt;br /&gt;11:26:38 AM      eth0      4.00      0.00      0.45      0.00      0.00      0.00      0.00&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Today, I'll introduce the erlang-sar package that's able to retrieve information from the sar command.&lt;br /&gt;&lt;br /&gt;The application is composed of a collector "sar_collector", a helper module "sar_values" and the main module "sar".&lt;br /&gt;Here comes a quick sample session:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;% Starting the collector&lt;br /&gt;sar_collect:start().&lt;br /&gt;&lt;br /&gt;% Retrieving the data&lt;br /&gt;sar:stats(cpu).&lt;br /&gt;[{cpu,idle,&lt;&lt;"98.62"&gt;&gt;},&lt;br /&gt; {cpu,steal,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {cpu,iowait,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {cpu,system,&lt;&lt;"0.18"&gt;&gt;},&lt;br /&gt; {cpu,nice,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {cpu,user,&lt;&lt;"1.20"&gt;&gt;}]&lt;br /&gt;&lt;br /&gt;% Retrieving more data&lt;br /&gt;sar:stats([cpu,mem]).&lt;br /&gt;[{swap,swpcad,&lt;&lt;"33236"&gt;&gt;},&lt;br /&gt; {swap,usage,&lt;&lt;"64.72"&gt;&gt;},&lt;br /&gt; {swap,used,&lt;&lt;"389872"&gt;&gt;},&lt;br /&gt; {swap,free,&lt;&lt;"212492"&gt;&gt;},&lt;br /&gt; {mem,kbcached,&lt;&lt;"84496"&gt;&gt;},&lt;br /&gt; {mem,kbbuffers,&lt;&lt;"63408"&gt;&gt;},&lt;br /&gt; {mem,memused,&lt;&lt;"98.78"&gt;&gt;},&lt;br /&gt; {mem,kbmemused,&lt;&lt;"508984"&gt;&gt;},&lt;br /&gt; {mem,kbmemfree,&lt;&lt;"6308"&gt;&gt;},&lt;br /&gt; {cpu,idle,&lt;&lt;"97.83"&gt;&gt;},&lt;br /&gt; {cpu,steal,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {cpu,iowait,&lt;&lt;"0.75"&gt;&gt;},&lt;br /&gt; {cpu,system,&lt;&lt;"0.20"&gt;&gt;},&lt;br /&gt; {cpu,nice,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {cpu,user,&lt;&lt;"1.22"&gt;&gt;}]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The module "sar_values" also export an "extractor" function that can be used to build fun()s:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;% build a Mem fun()&lt;br /&gt;Mem = sar_values:extractor(mem).&lt;br /&gt;&lt;br /&gt;% Calling Mem fun() on sar:stats()&lt;br /&gt;Mem(sar:stats([cpu,mem])).&lt;br /&gt;[{kbcached,&lt;&lt;"84496"&gt;&gt;},&lt;br /&gt; {kbbuffers,&lt;&lt;"63480"&gt;&gt;},&lt;br /&gt; {memused,&lt;&lt;"98.77"&gt;&gt;},&lt;br /&gt; {kbmemused,&lt;&lt;"508976"&gt;&gt;},&lt;br /&gt; {kbmemfree,&lt;&lt;"6316"&gt;&gt;}]&lt;br /&gt;&lt;br /&gt;% Calling it on sar:stats()&lt;br /&gt;Mem(sar:stats()).&lt;br /&gt;[{kbcached,&lt;&lt;"84496"&gt;&gt;},&lt;br /&gt; {kbbuffers,&lt;&lt;"63520"&gt;&gt;},&lt;br /&gt; {memused,&lt;&lt;"98.80"&gt;&gt;},&lt;br /&gt; {kbmemused,&lt;&lt;"509100"&gt;&gt;},&lt;br /&gt; {kbmemfree,&lt;&lt;"6192"&gt;&gt;}]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With this package you have access to all the data sar can export for you.&lt;br /&gt;Here's the "sar.erl" code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(sar).&lt;br /&gt;&lt;br /&gt;-export([systat/0, stats/0, stats/1, option/1]).&lt;br /&gt;-export([extract/1]).&lt;br /&gt;-define(OPTIONS, "-u -r -v -c -q -n DEV").&lt;br /&gt;-define(DATA, "/tmp/last").&lt;br /&gt;&lt;br /&gt;systat() -&gt;&lt;br /&gt; Cmd = "sadf " ++ ?DATA ++ " -- " ++ ?OPTIONS,&lt;br /&gt; execute(".", Cmd).&lt;br /&gt;&lt;br /&gt;stats() -&gt;&lt;br /&gt; Cmd = "sadf " ++ ?DATA ++ " -- " ++ ?OPTIONS,&lt;br /&gt; {ok, _, Bin} = execute(".", Cmd),&lt;br /&gt; extract(Bin).&lt;br /&gt;&lt;br /&gt;stats(List) when is_list(List) -&gt;&lt;br /&gt; Args = lists:foldl(fun(X, Acc) -&gt; case option(X) of&lt;br /&gt;     error -&gt;&lt;br /&gt;      Acc;&lt;br /&gt;     T -&gt;&lt;br /&gt;      [ $ , T | Acc ]&lt;br /&gt;    end end, [], List),&lt;br /&gt; Cmd = "sadf " ++ ?DATA ++ " -- " ++ lists:reverse(Args),&lt;br /&gt; {ok, _, Bin} = execute(".", lists:flatten(Cmd)),&lt;br /&gt; extract(Bin);&lt;br /&gt;&lt;br /&gt;stats(Elem) -&gt;&lt;br /&gt; stats([Elem]).&lt;br /&gt;&lt;br /&gt;option(cpu) -&gt;&lt;br /&gt; "-u";&lt;br /&gt;option(disk) -&gt;&lt;br /&gt; "-d";&lt;br /&gt;option(sock) -&gt;&lt;br /&gt; "-n SOCK";&lt;br /&gt;option(eth0) -&gt;&lt;br /&gt; "-n DEV";&lt;br /&gt;option(eth1) -&gt;&lt;br /&gt; "-n DEV";&lt;br /&gt;option(eth2) -&gt;&lt;br /&gt; "-n DEV";&lt;br /&gt;option(proc) -&gt;&lt;br /&gt; "-c";&lt;br /&gt;option(run) -&gt;&lt;br /&gt; "-q";&lt;br /&gt;option(mem) -&gt;&lt;br /&gt; "-r";&lt;br /&gt;option(inode) -&gt;&lt;br /&gt; "-v";&lt;br /&gt;option(switch) -&gt;&lt;br /&gt; "-w";&lt;br /&gt;option(swaping) -&gt;&lt;br /&gt; "-W";&lt;br /&gt;option(_) -&gt;&lt;br /&gt; error.&lt;br /&gt;&lt;br /&gt;execute(_Host, Cmd) -&gt;&lt;br /&gt; Port = open_port({spawn, Cmd}, [ exit_status, binary ] ),&lt;br /&gt; wait(Port, []).&lt;br /&gt;&lt;br /&gt;wait(Port, Content) -&gt;&lt;br /&gt; receive &lt;br /&gt;  {Port, {data, BinData}} -&gt;&lt;br /&gt;   %error_logger:info_msg("dump:~n~p~n", [BinData]),&lt;br /&gt;   NewContent = [ BinData | Content ],&lt;br /&gt;   wait(Port, NewContent);&lt;br /&gt;&lt;br /&gt;  {Port, {exit_status, Status}} -&gt;&lt;br /&gt;   %error_logger:info_msg("exit_code: ~p~n", [Status]),&lt;br /&gt;   {ok, Status, Content};&lt;br /&gt;&lt;br /&gt;  {Port, eof} -&gt;&lt;br /&gt;   %error_logger:info_msg("Port closed"),&lt;br /&gt;   port_close(Port),&lt;br /&gt;   {ok, eof, Content};&lt;br /&gt;&lt;br /&gt;  {Port, exit} -&gt;&lt;br /&gt;   error_logger:info_msg("Received : ~p~n", [Port]),&lt;br /&gt;   Content&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;extract(Bin) -&gt;&lt;br /&gt; sar_values:extract(iolist_to_binary(Bin)).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can see the "option/1" function that let you convert atoms into command line arguments easily. I use also this function to test if sar is able to handle a specific parameter. For example and with the help of my &lt;a href="http://easyerl.blogspot.com/2008/07/my-mochiweb-webservice.html"&gt;webservice&lt;/a&gt; I can query remote stats easily:&lt;pre&gt;http://monitoring.lan/stats/q/cpu/servername&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's the "sar_collect" module&lt;pre&gt;&lt;br /&gt;-module(sar_collect).&lt;br /&gt;&lt;br /&gt;-export([systat/1, sartime/1, start/0, start/1]).&lt;br /&gt;-export([extract/1]).&lt;br /&gt;        spawn(?MODULE, systat, []).&lt;br /&gt;&lt;br /&gt;start(Seconds) -&gt;&lt;br /&gt;        spawn(?MODULE, systat, [Seconds]).&lt;br /&gt;&lt;br /&gt;% update the file every second        &lt;br /&gt;systat(0) -&gt; &lt;br /&gt;        loop(1);&lt;br /&gt;&lt;br /&gt;systat(Seconds) -&gt;&lt;br /&gt;        loop(Seconds).&lt;br /&gt;&lt;br /&gt;%update the file every 59 seconds&lt;br /&gt;systat() -&gt;&lt;br /&gt;        loop(59).&lt;br /&gt;&lt;br /&gt;loop(Seconds) when Seconds &lt; 60 -&gt;&lt;br /&gt; Cmd = lists:flatten([ "sar -o /tmp/last.tmp ", integer_to_list(Seconds),  " 1" ]),&lt;br /&gt; execute(".", Cmd),&lt;br /&gt; file:rename("/tmp/last.tmp", "/tmp/last"),&lt;br /&gt; timer:sleep(60 - Seconds),&lt;br /&gt; receive&lt;br /&gt;  stop -&gt;&lt;br /&gt;   exit(normal);&lt;br /&gt;&lt;br /&gt;  {interval, NewSeconds} -&gt;&lt;br /&gt;   loop(NewSeconds);&lt;br /&gt;&lt;br /&gt;  _A -&gt;&lt;br /&gt;   loop(Seconds)&lt;br /&gt;&lt;br /&gt; after 0 -&gt;&lt;br /&gt;  loop(Seconds) &lt;br /&gt;&lt;br /&gt; end;&lt;br /&gt;&lt;br /&gt;%default update 20 seconds (arbitrary chosen)&lt;br /&gt;loop(_Seconds) -&gt;&lt;br /&gt; loop(20).&lt;br /&gt;&lt;br /&gt;execute(Host, Cmd) -&gt;&lt;br /&gt; Port = open_port({spawn, Cmd}, [ {cd, Host}, exit_status, binary ] ),&lt;br /&gt; wait(Port, []).&lt;br /&gt;&lt;br /&gt;wait(Port, Content) -&gt;&lt;br /&gt; receive &lt;br /&gt;  {Port, {data, _BinData}} -&gt;&lt;br /&gt;   wait(Port, Content);&lt;br /&gt;&lt;br /&gt;  {Port, {exit_status, _Status}} -&gt;&lt;br /&gt;   ok;&lt;br /&gt;&lt;br /&gt;  {Port, eof} -&gt;&lt;br /&gt;   port_close(Port),&lt;br /&gt;   Content;&lt;br /&gt;&lt;br /&gt;  {Port, exit} -&gt;&lt;br /&gt;   error_logger:info_msg("Received : ~p~n", [Port]),&lt;br /&gt;   Content&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally there is the "sar_values" source code:&lt;pre&gt;&lt;br /&gt;-module(sar_values).&lt;br /&gt;&lt;br /&gt;-export([extract/1, extractor/1, sort/1]).&lt;br /&gt;-export([parse/1, parse_value/2]).&lt;br /&gt;&lt;br /&gt;extract(Bin) -&gt;&lt;br /&gt; extract(Bin, []).&lt;br /&gt;&lt;br /&gt;extract(Bin, Stats) -&gt;&lt;br /&gt; case parse(Bin) of&lt;br /&gt;  {Class, Type, Rest} -&gt;&lt;br /&gt;   %io:format("~p.~p", [Class, Type]),&lt;br /&gt;   case  parse_value(Rest, &lt;&lt;&gt;&gt;) of&lt;br /&gt;    {more, Value, More} -&gt;&lt;br /&gt;     NewStats = [ {Class, Type, Value} | Stats ],&lt;br /&gt;     extract(More, NewStats);&lt;br /&gt;&lt;br /&gt;    {eof, Value} -&gt;&lt;br /&gt;     NewStats = [ {Class, Type, Value} | Stats ],&lt;br /&gt;     NewStats &lt;br /&gt;   end;&lt;br /&gt;&lt;br /&gt;  eof -&gt; &lt;br /&gt;   Stats &lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"%user", Rest/binary &gt;&gt;) -&gt; {cpu, user, Rest};&lt;br /&gt;parse(&lt;&lt;"%nice", Rest/binary&gt;&gt;) -&gt; {cpu, nice, Rest};&lt;br /&gt;parse(&lt;&lt;"%system", Rest/binary&gt;&gt;) -&gt; {cpu, system, Rest};&lt;br /&gt;parse(&lt;&lt;"%iowait", Rest/binary&gt;&gt;) -&gt; {cpu, iowait, Rest};&lt;br /&gt;parse(&lt;&lt;"%steal", Rest/binary&gt;&gt;) -&gt;  {cpu, steal, Rest};&lt;br /&gt;parse(&lt;&lt;"%idle", Rest/binary&gt;&gt;) -&gt; {cpu, idle, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"kbmemfree", Rest/binary&gt;&gt;) -&gt; {mem, kbmemfree, Rest};&lt;br /&gt;parse(&lt;&lt;"kbmemused", Rest/binary&gt;&gt;) -&gt; {mem, kbmemused, Rest};&lt;br /&gt;parse(&lt;&lt;"%memused", Rest/binary&gt;&gt;) -&gt; {mem, memused, Rest};&lt;br /&gt;parse(&lt;&lt;"kbbuffers", Rest/binary&gt;&gt;) -&gt; {mem, kbbuffers, Rest};&lt;br /&gt;parse(&lt;&lt;"kbcached", Rest/binary&gt;&gt;) -&gt; {mem, kbcached, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"kbswpfree", Rest/binary&gt;&gt;) -&gt; {swap, free, Rest};&lt;br /&gt;parse(&lt;&lt;"kbswpused", Rest/binary&gt;&gt;) -&gt; {swap, used, Rest};&lt;br /&gt;parse(&lt;&lt;"%swpused", Rest/binary&gt;&gt;) -&gt; {swap, usage, Rest};&lt;br /&gt;parse(&lt;&lt;"kbswpcad", Rest/binary&gt;&gt;) -&gt; {swap, swpcad, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"dentunusd", Rest/binary&gt;&gt;) -&gt; {inode, dentryunused, Rest};&lt;br /&gt;parse(&lt;&lt;"file-sz", Rest/binary&gt;&gt;) -&gt; {inode, fileopened, Rest};&lt;br /&gt;parse(&lt;&lt;"inode-sz", Rest/binary&gt;&gt;) -&gt; {inode, inodes, Rest};&lt;br /&gt;parse(&lt;&lt;"super-sz", Rest/binary&gt;&gt;) -&gt; {inode, super, Rest};&lt;br /&gt;parse(&lt;&lt;"%super-sz", Rest/binary&gt;&gt;) -&gt; {inode, superusage, Rest};&lt;br /&gt;parse(&lt;&lt;"dquot-sz", Rest/binary&gt;&gt;) -&gt; {inode, dquotsz, Rest};&lt;br /&gt;parse(&lt;&lt;"%dquot-sz", Rest/binary&gt;&gt;) -&gt; {inode, dquotszusage, Rest};&lt;br /&gt;parse(&lt;&lt;"rtsig-sz", Rest/binary&gt;&gt;) -&gt; {rtsig, count , Rest};&lt;br /&gt;parse(&lt;&lt;"%rtsig-sz", Rest/binary&gt;&gt;) -&gt; {rtsig, usage, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"totsck", Rest/binary&gt;&gt;) -&gt; {sock, total, Rest};&lt;br /&gt;parse(&lt;&lt;"tcpsck", Rest/binary&gt;&gt;) -&gt; {sock, tcp, Rest};&lt;br /&gt;parse(&lt;&lt;"udpsck", Rest/binary&gt;&gt;) -&gt; {sock, udp, Rest};&lt;br /&gt;parse(&lt;&lt;"rawsck", Rest/binary&gt;&gt;) -&gt; {sock, raw, Rest};&lt;br /&gt;parse(&lt;&lt;"ip-frag", Rest/binary&gt;&gt;) -&gt; {sock, ipfrag, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"runq-sz", Rest/binary&gt;&gt;) -&gt; {procs, running, Rest};&lt;br /&gt;parse(&lt;&lt;"plist-sz", Rest/binary&gt;&gt;) -&gt; {procs, total, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"ldavg-15", Rest/binary&gt;&gt;) -&gt; {load, min15, Rest};&lt;br /&gt;parse(&lt;&lt;"ldavg-1", Rest/binary&gt;&gt;) -&gt; {load, min1, Rest};&lt;br /&gt;parse(&lt;&lt;"ldavg-5", Rest/binary&gt;&gt;) -&gt; {load, min5, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"pswpin/s", Rest/binary&gt;&gt;) -&gt; {swaping, pswpin, Rest};&lt;br /&gt;parse(&lt;&lt;"pswpout/s", Rest/binary&gt;&gt;) -&gt; {swaping, pswpout, Rest};&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;"l0", Rest/binary&gt;&gt;)   -&gt; parsebis(Rest, l0);&lt;br /&gt;parse(&lt;&lt;"eth0", Rest/binary&gt;&gt;) -&gt; parsebis(Rest, eth0);&lt;br /&gt;parse(&lt;&lt;"eth1", Rest/binary&gt;&gt;) -&gt; parsebis(Rest, eth1);&lt;br /&gt;parse(&lt;&lt;"eth2", Rest/binary&gt;&gt;) -&gt; parsebis(Rest, eth2);&lt;br /&gt;&lt;br /&gt;parse(&lt;&lt;&gt;&gt;) -&gt; eof;&lt;br /&gt; &lt;br /&gt;parse(Bin) -&gt;&lt;br /&gt; {_, Next} = split_binary(Bin, 1), &lt;br /&gt; parse(Next).&lt;br /&gt;&lt;br /&gt;parsebis(&lt;&lt;"rxpck/s", Rest/binary&gt;&gt;,  Category) -&gt; {Category, rxpck, Rest};&lt;br /&gt;parsebis(&lt;&lt;"txpck/s", Rest/binary&gt;&gt;,  Category) -&gt; {Category, txpck, Rest};&lt;br /&gt;parsebis(&lt;&lt;"rxbyt/s", Rest/binary&gt;&gt;,  Category) -&gt; {Category, rxbyt, Rest};&lt;br /&gt;parsebis(&lt;&lt;"txbyt/s", Rest/binary&gt;&gt;,  Category) -&gt; {Category, txbyt, Rest};&lt;br /&gt;parsebis(&lt;&lt;"rxcmp/s", Rest/binary&gt;&gt;,  Category) -&gt; {Category, rxcmp, Rest};&lt;br /&gt;parsebis(&lt;&lt;"txcmp/s", Rest/binary&gt;&gt;,  Category) -&gt; {Category, txcmp, Rest};&lt;br /&gt;parsebis(&lt;&lt;"rxmcst/s", Rest/binary&gt;&gt;, Category) -&gt; {Category, rxmcst, Rest};&lt;br /&gt;parsebis(Bin, Category) -&gt; &lt;br /&gt; {_, Next} = split_binary(Bin, 1), &lt;br /&gt; parsebis(Next, Category).&lt;br /&gt;&lt;br /&gt;parse_value(&lt;&lt;$\t, Rest/binary&gt;&gt;, _Value) -&gt;&lt;br /&gt; parse_value(Rest, _Value);&lt;br /&gt;parse_value(&lt;&lt;$ , Rest/binary&gt;&gt;, _Value) -&gt;&lt;br /&gt; parse_value(Rest, _Value);&lt;br /&gt;&lt;br /&gt;parse_value(&lt;&lt;$\n, _Rest/binary&gt;&gt;, Value) -&gt;&lt;br /&gt; {more, Value, _Rest};&lt;br /&gt;&lt;br /&gt;parse_value(&lt;&lt;&gt;&gt;, Value) -&gt;&lt;br /&gt; {eof, Value};&lt;br /&gt;&lt;br /&gt;parse_value(Bin, Value) -&gt;&lt;br /&gt; {H, Next} = split_binary(Bin, 1),&lt;br /&gt; parse_value(Next, iolist_to_binary([Value, H])).&lt;br /&gt;&lt;br /&gt;extractor(Motif) -&gt;&lt;br /&gt; fun(L) when is_list(L) -&gt;&lt;br /&gt;  [ {Y, Z} || {X, Y, Z} &lt;- L, X == Motif]&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;sort(List) -&gt;&lt;br /&gt; lists:sort( fun({X, _V}, {Y, _W}) when X &lt; Y -&gt; &lt;br /&gt;   true; &lt;br /&gt;   (_A, _B) -&gt; false &lt;br /&gt;  end, List).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now that Erlang is R12B, I'm not so sure if "binary parsing code" is really as efficient as it can...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2665464757293330209?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2665464757293330209/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2665464757293330209' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2665464757293330209'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2665464757293330209'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/07/monitoring-your-servers-with-sysstat.html' title='Monitoring your servers with sysstat (sar)'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2891792412737653499</id><published>2008-06-26T12:03:00.004+02:00</published><updated>2008-07-08T09:52:58.683+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rant'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='parsing'/><title type='text'>Parsing Binaries with erlang, lamers inside</title><content type='html'>Edit: I don't want to offend anyone with the following, this is just an expression of what i encounter every day with people that do technology but don't know anything. I don't say anyone on the mailling list is a lamer, I say that question like the one evocated here exists because there's too much ignorance in the technology world. And lastly, don't forget this is a personal rant...&lt;br /&gt;&lt;br /&gt;It's seems that there's a really high expectation  on &lt;a href="http://www.erlang.org/pipermail/erlang-questions/2008-June/036166.html"&gt;"parsing binaries" with erlang&lt;/a&gt; and reach others languages performance. For me this is a complete nonsense.&lt;br /&gt;What's the meaning of "parsing a binary", a binary is not a text, it's a binary, a sequence of bytes... &lt;br /&gt;A sequence must be defined by its length. You must know before reading anything, the size that'll be needed store what's coming.&lt;br /&gt;Every crap software you can find has always prefered to use strcpy instead of memcpy. Whatever the language you use, you MUST know the size of what you're working with, this is not an advice this is mandatory.&lt;br /&gt;&lt;br /&gt;From the post above, you can find that the only delimiter seems to be "\r\n". So if someone sends you 4Gb of data not ending with "\r\n" you'll keep reading it... (and of course blow your memory because this was not supposed to be) &lt;br /&gt;&lt;br /&gt;While working at low level with C and flex scanners, I've always ask me this question: "What's the max size of the element I can accept ?". This simple question helps me build software that don't break with a simple 'perl print Ax60000' trick...&lt;br /&gt;&lt;br /&gt;So is HTTP badly designed, because delimiters are "\r\n" and headers can spread on multiple lines ? The answer is absolutely YES.&lt;br /&gt;Was'it difficult to build something more secure, using prefixed elements with their size ? The answer is absolutely NO ! (take &lt;a href="http://easyerl.blogspot.com/2007/07/erlang-and-jboss-talking-ajp13-part-i.html"&gt;ajp13 for example&lt;/a&gt;...)&lt;br /&gt;&lt;br /&gt;Now that erlang is becoming more and more popular, lamers are lurking in the erlang direction. This is life, but will the erlang mailling list suffer from this ? The answer is yes :/&lt;br /&gt;&lt;br /&gt;Someone with knowledge must not try to resolve someone's else problem, he must help him by asking the good question. (do you know the size a priori ?)&lt;br /&gt;&lt;br /&gt;Why parsing binaries in java is faster than erlang ? Who cares, since parsing binaries is of course stupid !&lt;br /&gt;Parsing real world protocol with erlang is lightning fast, both for writing and for executing. So teach lamers how to build real protocols and don't try help them with some trickery.&lt;br /&gt;&lt;br /&gt;That's my rant for today :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2891792412737653499?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2891792412737653499/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2891792412737653499' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2891792412737653499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2891792412737653499'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/06/parsing-binaries-with-erlang-lamers.html' title='Parsing Binaries with erlang, lamers inside'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3592269825987137569</id><published>2008-06-20T09:19:00.000+02:00</published><updated>2008-06-20T09:20:00.741+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quick tip'/><category scheme='http://www.blogger.com/atom/ns#' term='join'/><category scheme='http://www.blogger.com/atom/ns#' term='lists'/><title type='text'>Quick Tip, list join</title><content type='html'>Another "lists:join" :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Join = fun([X|Rest], D) -&amp;gt; &lt;br /&gt;  [ X | [ [D,E] || E &amp;lt;- Rest ] ] &lt;br /&gt;  end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Usage:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;io:format("~s~n", [Join(["a", "b", "cde", "h", "k", "lm"], $,)]).&lt;br /&gt;a,b,cde,h,k,lm&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3592269825987137569?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3592269825987137569/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3592269825987137569' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3592269825987137569'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3592269825987137569'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/06/quick-tip-list-join.html' title='Quick Tip, list join'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-4377781961682745041</id><published>2008-06-18T13:38:00.001+02:00</published><updated>2008-06-18T13:38:51.530+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mobile'/><category scheme='http://www.blogger.com/atom/ns#' term='ghostscript'/><title type='text'>Ubuntu and Ghostscript</title><content type='html'>On my development box, I've recently upgraded my ubuntu. The ghostscript package was also upgraded, but my erlang webservice wasn't able anymore to draw any mobile tag...&lt;br /&gt;&lt;br /&gt;I've found that the new ghostscript binary 'gs' has new command line parameters that are incompatible with their previous version...&lt;br /&gt;&lt;br /&gt;I used to initialize gs like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; Cmd = "gs -sDEVICE=pngalpha -q -dNOPLATFONTS -dNOPAUSE -dGraphicsAlphaBits=2 -sOutputFile=- -",&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But that no longer works since this flushing is done only when the process quits...&lt;br /&gt;&lt;br /&gt;The correct command line is then:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; Cmd = "gs -sDEVICE=pngalpha -q -dGraphicsAlphaBits=2 -sOutputFile=%stdout -dNOPROMPT",&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Everything works fine now, but I've re-read the very long Ghostscript documentation, and the solution comes from &lt;a href="http://pages.cs.wisc.edu/%7Eghost/doc/cvs/Use.htm#Parameter_switches"&gt;this page&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-4377781961682745041?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/4377781961682745041/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=4377781961682745041' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4377781961682745041'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4377781961682745041'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/06/ubuntu-and-ghostscript.html' title='Ubuntu and Ghostscript'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3589219738909897871</id><published>2008-06-13T10:09:00.001+02:00</published><updated>2008-06-13T10:09:07.943+02:00</updated><title type='text'>Quick Bash Script for Checking HTTP Headers</title><content type='html'>Whenever it comes to efficiently configure Web Servers and setting  headers like ETAg, Cache-Control, or Expires and you have more than one server, you need to check them all since any user may hit different servers.&lt;br /&gt;&lt;br /&gt;This script is meant to call wget on every IP returned by dig and display http response headers.    &lt;br /&gt;Usage is simple:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;./script.sh http://www.example.com/ressource/with-long-cache-and-no-etag/big-image.jpg&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's the code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#!/bin/bash&lt;br /&gt;&lt;br /&gt;BIN=${0##*/}&lt;br /&gt;URL=${1?usage: $BIN url}&lt;br /&gt;HOST=${URL#http://*}&lt;br /&gt;REQ=${HOST#*/}&lt;br /&gt;HOST=${HOST%%/*}&lt;br /&gt;&lt;br /&gt;function check&lt;br /&gt;{&lt;br /&gt;	wget -S --header="Host: $HOST" "http://$line/$REQ" -O/dev/null&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;dig +short $HOST | \&lt;br /&gt;while read line&lt;br /&gt;do&lt;br /&gt;	case $line in	&lt;br /&gt;		[a-z]*)&lt;br /&gt;		;;&lt;br /&gt;		[0-9]*.[0-9]*.[0-9]*)&lt;br /&gt;			echo === Testing $HOST with IP $line&lt;br /&gt;			check $line&lt;br /&gt;		;;&lt;br /&gt;		*)&lt;br /&gt;		;;&lt;br /&gt;	esac&lt;br /&gt;done&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3589219738909897871?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3589219738909897871/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3589219738909897871' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3589219738909897871'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3589219738909897871'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/06/quick-bash-script-for-checking-http.html' title='Quick Bash Script for Checking HTTP Headers'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2437865996644715166</id><published>2008-05-19T14:37:00.001+02:00</published><updated>2008-05-19T14:37:07.162+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='port'/><category scheme='http://www.blogger.com/atom/ns#' term='tail'/><category scheme='http://www.blogger.com/atom/ns#' term='monitoring'/><title type='text'>Monitoring log files with 'tail'</title><content type='html'>When you need to look for specific events from logfiles, your first idea is to use 'tail'. Tail is obviously the number one command that any sysadmin knows about.&lt;br /&gt;&lt;br /&gt;From the first version of Tail and nowadays, some really nice feature have been implemented, one of those is the "follow=name" feature...&lt;br /&gt;&lt;br /&gt;Since your erlang node will stay alive for many days, you'll end up meeting some logrotation tool that will replace the file you're lurking... So "follow=name" is for you !&lt;br /&gt;&lt;br /&gt;Extract from a manual page:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;     There are two ways to specify how you'd like to track files with&lt;br /&gt;     this option, but that difference is noticeable only when a&lt;br /&gt;     followed file is removed or renamed.  If you'd like to continue to&lt;br /&gt;     track the end of a growing file even after it has been unlinked,&lt;br /&gt;     use `--follow=descriptor'.  This is the default behavior, but it&lt;br /&gt;     is not useful if you're tracking a log file that may be rotated&lt;br /&gt;     (removed or renamed, then reopened).  In that case, use&lt;br /&gt;     `--follow=name' to track the named file by reopening it&lt;br /&gt;     periodically to see if it has been removed and recreated by some&lt;br /&gt;     other program.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Implementing this feature in pure erlang is of course possible, but why loose time when you can directly use the "tail" binary already installed on your system ?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(tail).&lt;br /&gt;-export([start/1, start/2, start/3, stop/1, snapshot/1, display/1, init/3]).&lt;br /&gt;&lt;br /&gt;start(File) -&amp;gt;&lt;br /&gt;	start(File, fun display/1, "/var/log").&lt;br /&gt;&lt;br /&gt;start(File, Callback) -&amp;gt;&lt;br /&gt;	Dir = "/var/log",&lt;br /&gt;	start(File, Callback, Dir).&lt;br /&gt;&lt;br /&gt;start(File, Callback, Dir) -&amp;gt;&lt;br /&gt;	spawn_link(?MODULE, init, [File, Callback, Dir]).&lt;br /&gt;&lt;br /&gt;snapshot(Pid) -&amp;gt;&lt;br /&gt;	Pid ! {snap, self() },&lt;br /&gt;	receive&lt;br /&gt;		{Port, Callback} -&amp;gt;&lt;br /&gt;			{Port, erlang:fun_info(Callback)};&lt;br /&gt;		_Any -&amp;gt;&lt;br /&gt;			_Any&lt;br /&gt;	end.&lt;br /&gt;&lt;br /&gt;stop(Pid) -&amp;gt;&lt;br /&gt;	Pid ! stop.&lt;br /&gt;&lt;br /&gt;init(File, Callback, Dir) -&amp;gt;&lt;br /&gt;	Cmd = "/usr/bin/tail --follow=name "++ File,&lt;br /&gt;	Port = open_port({spawn, Cmd}, [ {cd, Dir}, stderr_to_stdout, {line, 256}, exit_status, binary]), &lt;br /&gt;	tail_loop(Port, Callback).&lt;br /&gt;&lt;br /&gt;tail_loop(Port, Callback) -&amp;gt;&lt;br /&gt;	receive&lt;br /&gt;		{Port, {data, {eol, Bin}}} -&amp;gt;&lt;br /&gt;			Callback(Bin),&lt;br /&gt;			tail_loop(Port, Callback);&lt;br /&gt;&lt;br /&gt;		{Port, {data, {noeol, Bin}}} -&amp;gt;&lt;br /&gt;			Callback(Bin),&lt;br /&gt;			tail_loop(Port, Callback);&lt;br /&gt;&lt;br /&gt;		{Port, {data, Bin}} -&amp;gt;&lt;br /&gt;			Callback(Bin),&lt;br /&gt;			tail_loop(Port, Callback);&lt;br /&gt;&lt;br /&gt;		{Port, {exit_status, Status}} -&amp;gt;&lt;br /&gt;			{ok, Status};&lt;br /&gt;			%tail_loop(Port, Callback);&lt;br /&gt;	&lt;br /&gt;		{Port, eof} -&amp;gt;&lt;br /&gt;			port_close(Port),&lt;br /&gt;			{ok, eof};&lt;br /&gt;&lt;br /&gt;		{snap, Who} -&amp;gt;&lt;br /&gt;			Who ! { Port, Callback},&lt;br /&gt;			tail_loop(Port, Callback);&lt;br /&gt;&lt;br /&gt;		stop -&amp;gt;&lt;br /&gt;			port_close(Port),&lt;br /&gt;			{ok, stop};&lt;br /&gt;		&lt;br /&gt;		_Any -&amp;gt;&lt;br /&gt;			tail_loop(Port, Callback) &lt;br /&gt;	end.&lt;br /&gt;&lt;br /&gt;display(Bin) -&amp;gt;&lt;br /&gt;	Content = iolist_to_binary(Bin),&lt;br /&gt;	io:format("[INFO] ~s~n", [Content]).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Let's say you want to monitor "/var/log/messages", here's how you can do it:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Shell&amp;gt; Tail = tail:start("messages").&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This will display every new line (running in background) in your shell session.&lt;br /&gt;&lt;br /&gt;Now let's say you want to do some tricky things with every line, you can pass as a parameter a callback fun:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Shell&amp;gt; Pid = logger_new(). % an example&lt;br /&gt;Shell&amp;gt; Callback = fun(X) -&amp;gt; Pid ! {line, X} end. % sending a tuple to Pid&lt;br /&gt;Shell&amp;gt; Tail = tail:start("message", Callback).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally, you'll be able to hack the code and transform this method to "tail" multiple files since "tail" is able to watch more than one file...&lt;br /&gt;&lt;br /&gt;Quick tip :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;init(ListOfFiles, Callback, Dir) -&amp;gt;&lt;br /&gt;	Args = [ [ X, $ ] || X &amp;lt;- ListOfFiles ]&lt;br /&gt;	Cmd = "/usr/bin/tail --follow=name "++ lists:flatten(Args),&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Happy Tailing !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2437865996644715166?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2437865996644715166/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2437865996644715166' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2437865996644715166'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2437865996644715166'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/05/monitoring-log-files-with.html' title='Monitoring log files with &amp;#39;tail&amp;#39;'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2363001643313000265</id><published>2008-05-14T13:47:00.001+02:00</published><updated>2008-05-14T13:47:07.788+02:00</updated><title type='text'>Following your directories</title><content type='html'>While designing some "integrity checker" tool, I've found myself in trouble whenever I need to&lt;br /&gt;manage directories...&lt;br /&gt;&lt;br /&gt;It seems that erlang, for now, is not able to detect &lt;a href="http://en.wikipedia.org/wiki/Symbolic_link"&gt;"symbolic links"&lt;/a&gt; (Unix definition). Opening "kernel/include/file.hrl" holds the truth... The module "filelib" doesn't have any thing related to 'links' neither.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So if you have some links that point to "." you'll observe that "filelib:fold_files" follow the link many times (hopefully its stop somewhere) but you loose some precious time and increase disk accesses...&lt;br /&gt;&lt;br /&gt;Then I've rewrote the fold_files to detect links by searching in the path some identical elements:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;checktree([]) -&amp;gt;&lt;br /&gt;	true;&lt;br /&gt;checktree([_Elem,_Elem|_Rest]) -&amp;gt;&lt;br /&gt;	false;&lt;br /&gt;checktree([_|Rest]) -&amp;gt;&lt;br /&gt;	checktree(Rest).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is not really high quality but seems to work as expected...&lt;br /&gt;&lt;br /&gt;I've also added some "maxdeep" functionality that prevent the script to go too many deeper. &lt;br /&gt;&lt;br /&gt;I've named this module "wfile" for no particular reason :) and here's the full code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(wfile).&lt;br /&gt;-export([list/1, list/2]).&lt;br /&gt;&lt;br /&gt;list(Dir) -&amp;gt;&lt;br /&gt;	Fun = fun(X, _Acc) -&amp;gt; io:format("+ ~s~n", [X]) end,&lt;br /&gt;	list(Dir, Fun).&lt;br /&gt;&lt;br /&gt;list(Dir, Fun) -&amp;gt;&lt;br /&gt;	fold_files(Dir, Fun, []).&lt;br /&gt;&lt;br /&gt;fold_files(Dir, Fun, Acc) -&amp;gt;&lt;br /&gt;	fold_files(Dir, true, Fun, Acc).&lt;br /&gt;&lt;br /&gt;fold_files(Dir, Recursive, Fun, Acc) -&amp;gt;&lt;br /&gt;	fold_files1(Dir, Recursive, Fun, Acc).&lt;br /&gt;&lt;br /&gt;fold_files1(Dir, Recursive, Fun, Acc) -&amp;gt;&lt;br /&gt;	case file:list_dir(Dir) of&lt;br /&gt;		{ok, Files} -&amp;gt; fold_files2(Files, Dir, Recursive, Fun, Acc);&lt;br /&gt;		{error, _}  -&amp;gt; Acc&lt;br /&gt;	end.&lt;br /&gt;&lt;br /&gt;fold_files2([], _Dir, _Recursive, _Fun, Acc) -&amp;gt; &lt;br /&gt;	Acc;&lt;br /&gt;fold_files2([File|T], Dir, Recursive, Fun, Acc0) -&amp;gt;&lt;br /&gt;	FullName = filename:join(Dir, File),&lt;br /&gt;	case filelib:is_regular(FullName) of&lt;br /&gt;		true  -&amp;gt; &lt;br /&gt;			Acc = Fun(FullName, Acc0),&lt;br /&gt;			fold_files2(T, Dir, Recursive, Fun, Acc);&lt;br /&gt;&lt;br /&gt;		false -&amp;gt;&lt;br /&gt;			case Recursive and filelib:is_dir(FullName) and maxdeep(FullName, 6) of&lt;br /&gt;				true -&amp;gt;&lt;br /&gt;					Acc1 = fold_files1(FullName, Recursive, Fun, Acc0),&lt;br /&gt;					fold_files2(T, Dir, Recursive, Fun, Acc1);&lt;br /&gt;				false -&amp;gt;&lt;br /&gt;					fold_files2(T, Dir, Recursive, Fun, Acc0)&lt;br /&gt;		end&lt;br /&gt;    end.&lt;br /&gt;&lt;br /&gt;maxdeep(Filename, Max) -&amp;gt;&lt;br /&gt;	Elems = filename:split(Filename),&lt;br /&gt;	( Max &amp;gt; length(Elems) ) and checktree(Elems).&lt;br /&gt;&lt;br /&gt;checktree([]) -&amp;gt;&lt;br /&gt;	true;&lt;br /&gt;checktree([_Elem,_Elem|_Rest]) -&amp;gt;&lt;br /&gt;	false;&lt;br /&gt;checktree([_|Rest]) -&amp;gt;&lt;br /&gt;	checktree(Rest).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To end to story, here's how I used this module:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;erl&amp;gt; IC = integrity_checker:start().&lt;br /&gt;erl&amp;gt; wfile:list("/home/rolphin/tmp", fun(X, Acc) -&amp;gt; IC ! {add, X}, io:format("added: ~s~n", [X]) end).&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2363001643313000265?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2363001643313000265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2363001643313000265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2363001643313000265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2363001643313000265'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/05/following-your-directories.html' title='Following your directories'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1410723176222509038</id><published>2008-05-08T10:13:00.002+02:00</published><updated>2008-05-08T10:19:25.334+02:00</updated><title type='text'>EasyErl goes Mobile Friendly</title><content type='html'>Hi, I'm experiencing Mobile Tagging. &lt;br /&gt;You can see it at the left side of this page.&lt;br /&gt;Tags are generated dynamically, I'm using of course Erlang, Mochiweb, Ghostscript (rendering backend) and &lt;a href="http://datenfreihafen.org/projects/iec16022.html"&gt;iec16022&lt;/a&gt; to build the semacode.&lt;br /&gt;&lt;br /&gt;Since I own a Nokia N95, I can efficiently scan my own codes, but I don't know if it's the case for everyone :)&lt;br /&gt;&lt;br /&gt;So leave me a comment if you find those code hard to read...&lt;br /&gt;Thanks&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1410723176222509038?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1410723176222509038/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1410723176222509038' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1410723176222509038'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1410723176222509038'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/05/easyerl-goes-mobile-friendly.html' title='EasyErl goes Mobile Friendly'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-300866460237927234</id><published>2008-03-09T17:18:00.004+01:00</published><updated>2008-03-09T17:30:45.330+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='high order functions'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><title type='text'>Directories Recursively and Simple Binary Matching</title><content type='html'>Hi, It has been a long time :)&lt;br /&gt;&lt;br /&gt;So today two simple things first, some high order fun to work on tree:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(dir).&lt;br /&gt;-export([create/1]).&lt;br /&gt;&lt;br /&gt;create(List) -&gt;&lt;br /&gt;        H = fun(X) -&gt;&lt;br /&gt;                fun(Y) -&gt;&lt;br /&gt;                        Dir = filename:join([X,Y]),&lt;br /&gt;                        io:format("mkdir(~s)~n", [Dir]),&lt;br /&gt;                        Dir&lt;br /&gt;                        end&lt;br /&gt;                end,&lt;br /&gt;        build(fun(X) -&gt; X end, H,  List).&lt;br /&gt;&lt;br /&gt;build(_Fun, _Builder, []) -&gt;&lt;br /&gt;        ok;&lt;br /&gt;build(Fun, Builder, [Elem|List]) -&gt;&lt;br /&gt;        NewFun = Builder(Fun(Elem)),&lt;br /&gt;        build(NewFun, Builder, List).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A sample session:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;5&gt; dir:create(["ab","cd", "ef","12", "35", "av"]).&lt;br /&gt;mkdir(ab/cd)&lt;br /&gt;mkdir(ab/cd/ef)&lt;br /&gt;mkdir(ab/cd/ef/12)&lt;br /&gt;mkdir(ab/cd/ef/12/35)&lt;br /&gt;mkdir(ab/cd/ef/12/35/av)&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;What's interesting is the Builder fun that construct the fun that will be called the next time.&lt;br /&gt;That way we know were we are in the tree... The last Fun has all the knowledge of its ancestors :)&lt;br /&gt;&lt;br /&gt;Now a simple binary matcher code, I need it while extracting values from regex results. It takes a list of offsets and returns what's inside:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;slice(Slices, Bin) -&gt;&lt;br /&gt;       slice(Slices, [], Bin).&lt;br /&gt;&lt;br /&gt;slice([], Acc, _Bin) -&gt;&lt;br /&gt;       lists:reverse(Acc);&lt;br /&gt;slice([ {Start, Stop} | Rest ], Acc, Bin) -&gt;&lt;br /&gt;       Len = Stop - Start,&lt;br /&gt;       &lt;&lt;_:Start/binary,Value:Len/binary,_/binary&gt;&gt; = Bin,&lt;br /&gt;       slice(Rest, [ Value | Acc ], Bin).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A sample session:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;8&gt; matcher:slice([{1,5},{8,10}], &lt;&lt;"this is a not a solution"&gt;&gt;).&lt;br /&gt;[&lt;&lt;"his "&gt;&gt;,&lt;&lt;"a "&gt;&gt;]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I hope that someone will find this valuable...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-300866460237927234?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/300866460237927234/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=300866460237927234' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/300866460237927234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/300866460237927234'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2008/03/directories-recursively-and-simple.html' title='Directories Recursively and Simple Binary Matching'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-38584156061236574</id><published>2007-12-20T09:55:00.002+01:00</published><updated>2008-03-09T17:18:55.080+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='googlepages'/><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><category scheme='http://www.blogger.com/atom/ns#' term='DNS'/><category scheme='http://www.blogger.com/atom/ns#' term='download'/><title type='text'>Server DNS problems, and no longer available downloads :/</title><content type='html'>My own box has some troubles with its DNS entries, this means that you can no longer reach the LibTre (treregex-0.7)...&lt;br /&gt;&lt;br /&gt;I've set up an googlepage to store it in the mean time: &lt;a href="http://easyerl.googlepages.com/"&gt;easyerl.googlepages.com.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Why did I take some much time to fix it ? That's because I've left Paris to work in the South of France, exactly "sur la côté d'Azur". &lt;br /&gt;&lt;br /&gt;I'll publish some other simple things in the near future... So you just need to subscribe to my Feed :).&lt;br /&gt;&lt;br /&gt;Download &lt;a href="http://easyerl.googlepages.com/treregex-0.7.tar.gz"&gt;libTre.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;PS: Now if you read the &lt;a href="http://www.erlang.org/pipermail/erlang-questions/2007-December/031970.html"&gt;mailing list&lt;/a&gt; you'll be able to retrieve the package...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-38584156061236574?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/38584156061236574/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=38584156061236574' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/38584156061236574'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/38584156061236574'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/12/server-dns-problems-and-availabe.html' title='Server DNS problems, and no longer available downloads :/'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3817533897960809712</id><published>2007-11-19T21:53:00.000+01:00</published><updated>2007-11-19T22:18:58.366+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='nrpe'/><category scheme='http://www.blogger.com/atom/ns#' term='monitoring'/><category scheme='http://www.blogger.com/atom/ns#' term='nagios'/><title type='text'>NAGIOS (beurk) nrpe support for erlang</title><content type='html'>NAGIOS a pretty bad software uses a pretty bad protocol, but NAGIOS seems to be installed everywhere... &lt;br /&gt;I needed a way to bypass its really poor scheduling process, and naturally erlang comes to my rescue... But everything is not so simple.&lt;br /&gt;&lt;br /&gt;NRPE this horrible protocols uses fixed length packets (from the code the 2 last characters are never sets to 0, sizeof seems to be really misunderstood by the nagios developer :p).&lt;br /&gt;&lt;br /&gt;But NRPE is another crap CRC32 code, and for efficiency and time saving I didn't wanted to reimplemented it in Erlang, so I wrote a nrpe_crc32 port...&lt;br /&gt;&lt;br /&gt;Here's the crc32 code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;static unsigned long crc32_table[256];&lt;br /&gt;&lt;br /&gt;typedef struct packet_struct&lt;br /&gt;{&lt;br /&gt; int16_t   packet_version;&lt;br /&gt; int16_t   packet_type;&lt;br /&gt; u_int32_t crc32_value;&lt;br /&gt; int16_t   result_code;&lt;br /&gt; char      buffer[MAX_PACKETBUFFER_LENGTH];&lt;br /&gt;} packet;&lt;br /&gt;&lt;br /&gt;/* build the crc table - must be called before calculating the crc value */&lt;br /&gt;void generate_crc32_table(void){&lt;br /&gt; unsigned long crc, poly;&lt;br /&gt; int i, j;&lt;br /&gt;&lt;br /&gt; poly=0xEDB88320L;&lt;br /&gt; for(i=0;i&lt;256;i++){&lt;br /&gt;  crc=i;&lt;br /&gt;  for(j=8;j&gt;0;j--){&lt;br /&gt;   if(crc &amp; 1)&lt;br /&gt;    crc=(crc&gt;&gt;1)^poly;&lt;br /&gt;   else&lt;br /&gt;    crc&gt;&gt;=1;&lt;br /&gt;  }&lt;br /&gt;  crc32_table[i]=crc;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; return;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/* calculates the CRC 32 value for a buffer */&lt;br /&gt;unsigned long calculate_crc32(char *buffer, unsigned int buffer_size){&lt;br /&gt; register unsigned long crc;&lt;br /&gt; int this_char;&lt;br /&gt; int current_index;&lt;br /&gt;&lt;br /&gt; crc=0xFFFFFFFF;&lt;br /&gt;&lt;br /&gt; for(current_index=0;current_index&lt;buffer_size;current_index++){&lt;br /&gt;  this_char=(int)buffer[current_index];&lt;br /&gt;  crc=((crc&gt;&gt;8) &amp; 0x00FFFFFF) ^ crc32_table[(crc ^ this_char) &amp; 0xFF];&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; return (crc ^ 0xFFFFFFFF);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;unsigned long test(const char *value)&lt;br /&gt;{&lt;br /&gt; return calculate_crc32((char *) value, strlen(value));&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The port_driver:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/* port_driver.c */&lt;br /&gt;&lt;br /&gt;#include "erl_driver.h"&lt;br /&gt;&lt;br /&gt;extern void generate_crc32_table(void);&lt;br /&gt;extern unsigned long calculate_crc32(char *, unsigned int);&lt;br /&gt;&lt;br /&gt;typedef struct {&lt;br /&gt; ErlDrvPort port;&lt;br /&gt;} crc32_data;&lt;br /&gt;&lt;br /&gt;static ErlDrvData crc32_drv_start(ErlDrvPort port, char *buff)&lt;br /&gt;{&lt;br /&gt; crc32_data* d = (crc32_data*)driver_alloc(sizeof(crc32_data));&lt;br /&gt; d-&gt;port = port;&lt;br /&gt;&lt;br /&gt; /* init crc32 table */&lt;br /&gt; generate_crc32_table();&lt;br /&gt; return (ErlDrvData) d;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static void crc32_drv_stop(ErlDrvData handle)&lt;br /&gt;{&lt;br /&gt; driver_free((char*)handle);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static void crc32_drv_output(ErlDrvData handle, char *buff, int bufflen)&lt;br /&gt;{&lt;br /&gt; crc32_data* d = (crc32_data*)handle;&lt;br /&gt;&lt;br /&gt; char fn = buff[0];&lt;br /&gt; char *arg = &amp;buff[1];&lt;br /&gt; unsigned long res;&lt;br /&gt;&lt;br /&gt; switch (fn) {&lt;br /&gt;  case 1:&lt;br /&gt;   res = calculate_crc32(arg, bufflen - 1);&lt;br /&gt;   driver_output(d-&gt;port, (char *) &amp;res, (sizeof(unsigned long)));&lt;br /&gt;   break;&lt;br /&gt;  default:&lt;br /&gt;   break;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;ErlDrvEntry crc32_driver_entry = {&lt;br /&gt; NULL,                       /* F_PTR init, N/A */&lt;br /&gt; crc32_drv_start,          /* L_PTR start, called when port is opened */&lt;br /&gt; crc32_drv_stop,           /* F_PTR stop, called when port is closed */&lt;br /&gt; crc32_drv_output,         /* F_PTR output, called when erlang has sent */&lt;br /&gt; NULL,                       /* F_PTR ready_input, called when input descriptor ready */&lt;br /&gt; NULL,                       /* F_PTR ready_output, called when output descriptor ready */&lt;br /&gt; "crc32_drv",              /* char *driver_name, the argument to open_port */&lt;br /&gt; NULL,                       /* F_PTR finish, called when unloaded */&lt;br /&gt; NULL,                       /* F_PTR control, port_command callback */&lt;br /&gt; NULL,                       /* F_PTR timeout, reserved */&lt;br /&gt; NULL                        /* F_PTR outputv, reserved */&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;DRIVER_INIT(crc32_drv) /* must match name in driver_entry */&lt;br /&gt;{&lt;br /&gt; return &amp;crc32_driver_entry;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The crc32 module, initializing the lib, and calling the crc32 fun:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;-module(crc32).&lt;br /&gt;&lt;br /&gt;-export([start/0,init/1,compute/1]).&lt;br /&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt; start("crc32_drv").&lt;br /&gt;&lt;br /&gt;start(SharedLib) -&gt;&lt;br /&gt; case erl_ddll:load_driver(".", SharedLib) of&lt;br /&gt;  ok -&gt; ok;&lt;br /&gt;  {error, already_loaded} -&gt; ok;&lt;br /&gt;  _E -&gt;  io:format("Error: ~p~n", [_E]),&lt;br /&gt;   exit({error, could_not_load_driver})&lt;br /&gt; end,&lt;br /&gt; spawn(?MODULE, init, [SharedLib]).&lt;br /&gt;&lt;br /&gt;init(SharedLib) -&gt;&lt;br /&gt; register(?MODULE, self()),&lt;br /&gt; Port = open_port({spawn, SharedLib}, [binary]),&lt;br /&gt; loop(Port).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;compute(X) -&gt;&lt;br /&gt; Bin = iolist_to_binary(X),&lt;br /&gt; call_port(&lt;&lt;1, Bin/binary&gt;&gt;).&lt;br /&gt;&lt;br /&gt;call_port(Msg) -&gt;&lt;br /&gt; ?MODULE ! {call, self(), Msg},&lt;br /&gt; receive&lt;br /&gt;  {?MODULE, Result} -&gt;&lt;br /&gt;   Result&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;loop(Port) -&gt;&lt;br /&gt; receive&lt;br /&gt;  {call, Caller, Msg} -&gt;&lt;br /&gt;   Port ! {self(), {command, Msg}},&lt;br /&gt;   receive&lt;br /&gt;    {Port, {data, Data}} -&gt;&lt;br /&gt;     Caller ! {?MODULE, decode(Data)}&lt;br /&gt;   end,&lt;br /&gt;   loop(Port);&lt;br /&gt;&lt;br /&gt;  stop -&gt;&lt;br /&gt;   Port ! {self(), close},&lt;br /&gt;   receive&lt;br /&gt;    {Port, closed} -&gt;&lt;br /&gt;     exit(normal)&lt;br /&gt;   end;&lt;br /&gt;&lt;br /&gt;  {'EXIT', Port, Reason} -&gt;&lt;br /&gt;   io:format("~p ~n", [Reason]),&lt;br /&gt;   exit(port_terminated)&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;% Also, Valid for Network &lt;br /&gt;decode(&lt;&amp;lt;U:32/big-unsigned&gt;&gt; = Bin) when is_binary(Bin) -&gt;&lt;br /&gt; U.&lt;br /&gt;&lt;br /&gt;decode(X) -&gt; X.&lt;br /&gt;  &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now the nrpe module, there you'll see why the nrpe is pure crap, fixed packet length for this type of tool is nonsense... &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;-module(nrpe).&lt;br /&gt;&lt;br /&gt;-export([encode/1, request/1, crc32/1, connect/1, connect/2]).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;encode(Bin) -&gt;&lt;br /&gt; { Crc, _} = crc32:compute(Bin),&lt;br /&gt; &lt;&amp;lt;Crc:32, Bin&gt;&gt;.&lt;br /&gt;&lt;br /&gt;request(Query) -&gt;&lt;br /&gt; Version = 2,&lt;br /&gt; Type = 1,&lt;br /&gt; Crc = 0,&lt;br /&gt; Code = 0,&lt;br /&gt; Blank = &lt;&amp;lt;0:32/unit:256&gt;&gt;, % 1024 bytes &lt;br /&gt; Q = iolist_to_binary(Query),&lt;br /&gt; Padlen = 1024 - size(Q),&lt;br /&gt; {C, _} = crc32:compute(&lt;br /&gt; &lt;&amp;lt;Version:16, Type:16, Crc:32, Code:16, Q/binary, 0, 0, Blank:Padlen/binary&gt;&gt;),&lt;br /&gt; &lt;&amp;lt;Version:16, Type:16, C:32, Code:16, Q/binary, 0, 0, Blank:Padlen/binary&gt;&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Building two binaries to only send one, is completely dump. But this is required... Thanks&lt;br /&gt;to nrpe...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;crc32(Bin) -&gt;&lt;br /&gt; {Crc, _} = crc32:compute(Bin),&lt;br /&gt; {Crc, Bin}.&lt;br /&gt;&lt;br /&gt;%&lt;br /&gt;% send_packet.packet_version=(int16_t)htons(NRPE_PACKET_VERSION_2);&lt;br /&gt;% send_packet.packet_type=(int16_t)htons(QUERY_PACKET);&lt;br /&gt;% strncpy(&amp;send_packet.buffer[0],query,MAX_PACKETBUFFER_LENGTH);&lt;br /&gt;% send_packet.buffer[MAX_PACKETBUFFER_LENGTH-1]='\x0';&lt;br /&gt;% &lt;br /&gt;% send_packet.crc32_value=(u_int32_t)0L;&lt;br /&gt;% calculated_crc32=calculate_crc32((char *)&amp;send_packet,sizeof(send_packet));&lt;br /&gt;% send_packet.crc32_value=(u_int32_t)htonl(calculated_crc32);&lt;br /&gt;&lt;br /&gt;%% #define QUERY_PACKET  1  /* id code for a packet containing a query */&lt;br /&gt;%% #define RESPONSE_PACKET  2  /* id code for a packet containing a response */&lt;br /&gt;%% &lt;br /&gt;%% #define NRPE_PACKET_VERSION_2   2               /* packet version identifier */&lt;br /&gt;%% #define NRPE_PACKET_VERSION_1 1  /* older packet version identifiers (no longer supported) */&lt;br /&gt;%% &lt;br /&gt;%% #define MAX_PACKETBUFFER_LENGTH 1024  /* max amount of data we'll send in one query/response */&lt;br /&gt;&lt;br /&gt;%% typedef struct packet_struct{&lt;br /&gt;%%  int16_t   packet_version;&lt;br /&gt;%%  int16_t   packet_type;&lt;br /&gt;%%  u_int32_t crc32_value;&lt;br /&gt;%%  int16_t   result_code;&lt;br /&gt;%%  char      buffer[MAX_PACKETBUFFER_LENGTH];&lt;br /&gt;%%         }packet;&lt;br /&gt;&lt;br /&gt;connect(Host) -&gt;&lt;br /&gt; connect(Host, 5666). &lt;br /&gt;&lt;br /&gt;connect(Host, Port) -&gt;&lt;br /&gt; case gen_tcp:connect(Host, Port, [binary, {active, false}]) of&lt;br /&gt;  {ok, Sock} -&gt;&lt;br /&gt;   Query = request("test"),&lt;br /&gt;   send(Sock, Query),&lt;br /&gt;   io:format("Response: '~s'~n", [recv(Sock)]), &lt;br /&gt;   close(Sock);&lt;br /&gt;&lt;br /&gt;  {error, Error} -&gt;&lt;br /&gt;   io:format("Connect-error: ~p~n", [Error])&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;send(Sock, Data) -&gt;&lt;br /&gt; case gen_tcp:send(Sock, Data) of&lt;br /&gt;  ok -&gt;&lt;br /&gt;   ok;&lt;br /&gt;&lt;br /&gt;  {error, Error} -&gt;&lt;br /&gt;   io:format("send-error: ~p~n", [Error])&lt;br /&gt; end.&lt;br /&gt;&lt;br /&gt;recv(Sock) -&gt;&lt;br /&gt; case gen_tcp:recv(Sock, 0, 2000) of&lt;br /&gt;  {ok, Packet} -&gt;&lt;br /&gt;   io:format("read: ~p~n", [Packet]),&lt;br /&gt;   decode(Packet);&lt;br /&gt;&lt;br /&gt;  {error, Error} -&gt;&lt;br /&gt;   io:format("recv-error: ~p~n", [Error])&lt;br /&gt; end.&lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;close(Sock) -&gt;&lt;br /&gt; gen_tcp:close(Sock).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;decode(&lt;&amp;lt;Version:16, Type:16, Crc:32, 0, 0, Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; io:format("Version: ~p, Type: ~p, Crc: ~p~n", [Version, Type, Crc]),&lt;br /&gt; decode_response(Rest).&lt;br /&gt;&lt;br /&gt;decode_response(Bin) -&gt;&lt;br /&gt; Len = msg_len(Bin, 0),&lt;br /&gt; {Msg, _} = split_binary(Bin, Len),&lt;br /&gt; Msg.&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;msg_len(&lt;&amp;lt;0, Rest/binary&gt;&gt;, Len) -&gt;&lt;br /&gt; Len;&lt;br /&gt;msg_len(Bin, Len) -&gt;&lt;br /&gt; {_, Next} = split_binary(Bin, 1),&lt;br /&gt; msg_len(Next, Len + 1).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I hope someone will find this interesting :p&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3817533897960809712?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3817533897960809712/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3817533897960809712' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3817533897960809712'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3817533897960809712'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/11/nagios-beurk-nrpe-support-for-erlang.html' title='NAGIOS (beurk) nrpe support for erlang'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-409143546385934805</id><published>2007-11-18T22:33:00.000+01:00</published><updated>2007-11-18T22:38:13.056+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><title type='text'>Treregex-0.7 download now</title><content type='html'>You can download the treregex-0.7 from &lt;a href="http://download.rolphin.info/content/treregex-0.7.tar.gz"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;To test it you need to install the libtre library, normally you can do this by using apt-get:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-apt-cache search libtre&lt;br /&gt;libtre-dev - development package for the libtre4 regexp matching library&lt;br /&gt;libtre4 - regexp matching library with approximate matching&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Install the libtre4 and libtre-dev, and then try to ./configure the treregex. You may need to edit Makefile adjusting path for your needs...&lt;br /&gt;&lt;br /&gt;And as usual, feedback is welcome :p&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-409143546385934805?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/409143546385934805/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=409143546385934805' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/409143546385934805'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/409143546385934805'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/11/treregex-07-download-now.html' title='Treregex-0.7 download now'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-814131757010017166</id><published>2007-11-18T21:43:00.000+01:00</published><updated>2007-11-18T22:28:53.585+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='network'/><category scheme='http://www.blogger.com/atom/ns#' term='digraph'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Digraph and your network, too easy</title><content type='html'>The digraph module can help you build directed graph (or not directed) very easily. I need to know the status of all my hosts within my network, and I make statistics about service availability.&lt;br /&gt;With the digraph module I am able to write links between my hosts, my hosts are: karoten, ultraten, muloten, arsen, masculen, colen, pollen.&lt;br /&gt;So I define them at the first time&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)425&gt; f(D), D = digraph:new(). % a new digraph&lt;br /&gt;{graph,22,23,24,true}&lt;br /&gt;(beta@karoten)426&gt; servers:add(D, [karoten,ultraten,muloten,arsen,masculen,colen,pollen]). &lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now I can manipulate my nodes (my servers)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)427&gt; servers:connect(D, karoten, [ultraten,{muloten,http}, arsen, {masculen,ssh}]).&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;karoten can reach ultraten, and muloten with http, arsen, and masculen with ssh.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)428&gt; servers:connect(D, colen, [{muloten,http}, arsen, {pollen,ssh}]).             &lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;colen can reach muloten with http, arsen, and pollen with ssh.&lt;br /&gt;&lt;br /&gt;So let's find colen links:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)429&gt; servers:links(D, colen).&lt;br /&gt;[{colen,muloten,http},{colen,arsen,[]},{colen,pollen,ssh}]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This exactly what I've written before, good ...&lt;br /&gt;&lt;br /&gt;And muloten links:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)430&gt; servers:links(D, muloten).&lt;br /&gt;[{karoten,muloten,http},{colen,muloten,http}]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is deduced from what I've describe before...&lt;br /&gt;&lt;br /&gt;Now let's imagine we want to find a way to reach one node from another:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)431&gt; digraph:get_path(D, karoten, arsen).&lt;br /&gt;[karoten,arsen]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Karoten seems to be connected with arsen.&lt;br /&gt;&lt;br /&gt;Let's create a new link, between ultraten and colen:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)434&gt; servers:connect(D, ultraten, colen). &lt;br /&gt;['$e'|7]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's try to reach pollen from karoten:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)435&gt; digraph:get_path(D, karoten, pollen).&lt;br /&gt;[karoten,ultraten,colen,pollen]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So the way is: thru ultraten, colen, karoten can reach pollen...&lt;br /&gt;&lt;br /&gt;Now let's design a more web design approach, with a firewall, a load balancer lb, and various httpd and application servers, finally databases:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)436&gt; servers:add(D, [firewall,lb,http1,http2,http3,app1,app2,app3,app4,db1,db2]).&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The firewall is directly connected to the load balancer:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)437&gt; servers:connect(D, firewall, lb).&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The load balancer distribute the load to three httpd:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)438&gt; servers:connect(D, lb, [http1,http2,http3]).&lt;br /&gt;ok&lt;br /&gt;(beta@karoten)439&gt; servers:connect(D, http1, [app1,app2,app3]).&lt;br /&gt;ok&lt;br /&gt;(beta@karoten)440&gt; servers:connect(D, http2, [app2,app3]).&lt;br /&gt;ok&lt;br /&gt;(beta@karoten)441&gt; servers:connect(D, http3, [app3]).&lt;br /&gt;ok&lt;br /&gt;(beta@karoten)442&gt; servers:connect(D, app3,[db1,db2]).&lt;br /&gt;ok&lt;br /&gt;(beta@karoten)443&gt; servers:connect(D, app2, [db1]).&lt;br /&gt;ok&lt;br /&gt;(beta@karoten)444&gt; servers:connect(D, app1, db2).&lt;br /&gt;['$e'|21]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Finally I can find a path between the firewall and the database 2:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(beta@karoten)445&gt; digraph:get_path(D, firewall, db2).&lt;br /&gt;[firewall,lb,http3,app3,db2]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now the code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;module(servers).&lt;br /&gt;&lt;br /&gt;-export([add/2,del/2,connect/3,links/2,reachable/2]).&lt;br /&gt;&lt;br /&gt;add(Graph, Servers) when list(Servers) -&gt;&lt;br /&gt; lists:foreach(fun(X) -&gt; digraph:add_vertex(Graph, X) end, Servers);&lt;br /&gt;&lt;br /&gt;add(Graph, Server) -&gt;&lt;br /&gt; digraph:add_vertex(Graph, Server).&lt;br /&gt;&lt;br /&gt;del(Graph, Servers) when list(Servers) -&gt;&lt;br /&gt; lists:foreach(fun(X) -&gt; digraph:del_vertex(Graph, X) end, Servers);&lt;br /&gt;&lt;br /&gt;del(Graph, Server) -&gt;&lt;br /&gt; digraph:del_vertex(Graph, Server).&lt;br /&gt;&lt;br /&gt;connect(_Graph, _Server, []) -&gt;&lt;br /&gt; ok;&lt;br /&gt;connect(Graph, Server, [ {S, L} | Servers ]) -&gt;&lt;br /&gt; digraph:add_edge(Graph, Server, S, L),&lt;br /&gt; connect(Graph, Server, Servers);&lt;br /&gt;connect(Graph, Server, [ S | Servers ]) -&gt;&lt;br /&gt; digraph:add_edge(Graph, Server, S),&lt;br /&gt; connect(Graph, Server, Servers);&lt;br /&gt; &lt;br /&gt;% connect(Graph, Server, Servers) when list(Servers) -&gt;&lt;br /&gt;%   lists:foreach(fun(X) -&gt; digraph:add_edge(Graph, Server, X) end, Servers);&lt;br /&gt;&lt;br /&gt;connect(Graph, Server, S) -&gt;&lt;br /&gt; digraph:add_edge(Graph, Server, S).&lt;br /&gt;&lt;br /&gt;links(Graph, Server) -&gt;&lt;br /&gt; lists:map(fun(X) -&gt; {_, S1, S2, Label} = digraph:edge(Graph, X), {S1, S2, Label} end, digraph:edges(Graph, Server)).&lt;br /&gt;&lt;br /&gt;reachable(Graph, Server) when list(Server) -&gt;&lt;br /&gt; digraph_utils:reachable(Server, Graph);&lt;br /&gt;reachable(Graph, Server) -&gt;&lt;br /&gt; digraph_utils:reachable([Server], Graph).&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-814131757010017166?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/814131757010017166/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=814131757010017166' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/814131757010017166'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/814131757010017166'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/11/digraph-and-your-network-too-easy.html' title='Digraph and your network, too easy'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1780157638906540068</id><published>2007-11-17T16:54:00.000+01:00</published><updated>2007-11-17T17:01:27.765+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tail'/><category scheme='http://www.blogger.com/atom/ns#' term='ssh'/><category scheme='http://www.blogger.com/atom/ns#' term='remote'/><category scheme='http://www.blogger.com/atom/ns#' term='shell'/><title type='text'>Experimenting with the erlang SSH support, or remote 'tail' with SSH...</title><content type='html'>While designing my monitoring tool, and working and treregex, I found the ssh documentation and realize that it can be very useful for my tool.&lt;br /&gt;&lt;br /&gt;A simple question needed to be answered, is the ssh module able to easily spawn a remote process for me ?&lt;br /&gt;To verify, I tried to build a remote tail module called ssh_tail :)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(ssh_tail).&lt;br /&gt;        &lt;br /&gt;-export([tail/3]).&lt;br /&gt;-define(TIMEOUT, 5000). &lt;br /&gt;&lt;br /&gt;tail(Host, User, Pass) -&gt;&lt;br /&gt; case ssh_cm:connect(Host, 22, [{user_dir, "/var/tmp/ssh"}, {user, User}, {password, Pass}]) of&lt;br /&gt;  {ok, CM} -&gt;&lt;br /&gt;   session(CM, fun(X) -&gt; io:format("-ssh: ~p~n", [X]) end);&lt;br /&gt;&lt;br /&gt;  Error -&gt;&lt;br /&gt;   Error&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;From the ssh documentation user_dir let you decide where you want to store keys, from my experience it's better to use a separate directory from the ~/.ssh. &lt;br /&gt;It happens that latest version of ssh add meta information to their files that the ssh module can't handle. (more on this in another post).&lt;br /&gt;&lt;br /&gt;For the test I wanted to do a "tail -f" on a specific file ie "/var/log/syslog".&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;session(CM, Callback) -&gt;&lt;br /&gt; case ssh_cm:session_open(CM, ?TIMEOUT) of&lt;br /&gt;  {ok, Channel} -&gt;&lt;br /&gt;   case ssh_cm:shell(CM, Channel) of &lt;br /&gt;    ok -&gt;&lt;br /&gt;     ssh_cm:send(CM, Channel, "tail --follow=name /var/log/syslog\n"),&lt;br /&gt;     ssh_loop(CM, Channel, Callback);&lt;br /&gt;&lt;br /&gt;    Error -&gt;&lt;br /&gt;     error_logger:error_msg("Error: ~p~n", [Error])&lt;br /&gt;   end;&lt;br /&gt;  Error -&gt;&lt;br /&gt;   error_logger:error_msg("Session Error: ~p~n", [Error])&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;ssh_cm is responsible for starting a shell, and sending commands to the remote shell process. I send&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;tail --follow=name /var/log/syslog\n&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Don't forget the final '\n' character, since you won't get any results if you don't send it :p &lt;br /&gt;(I didn't think of that while testing for the first time and think that the code didn't work at all...)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ssh_loop(CM, Channel, Callback) -&gt;&lt;br /&gt; receive&lt;br /&gt;  stop -&gt;&lt;br /&gt;   % Closing channel&lt;br /&gt;   % ssh_cm:detach(CM, ?TIMEOUT),&lt;br /&gt;   ssh_cm:close(CM, Channel);&lt;br /&gt;&lt;br /&gt;  {ssh_cm, CM, {data, _Channel, 0, Data}} -&gt;&lt;br /&gt;   Callback(Data), &lt;br /&gt;   ssh_loop(CM, Channel, Callback);&lt;br /&gt;&lt;br /&gt;  {ssh_cm, CM, {data, Channel, Type, Data}} -&gt;&lt;br /&gt;   io:format("extended (~p): ~p~n", [Type, Data]),&lt;br /&gt;   ssh_loop(CM, Channel, Callback);&lt;br /&gt;&lt;br /&gt;  {ssh_cm, CM, {closed, _Channel}} -&gt;&lt;br /&gt;   ssh_cm:detach(CM, ?TIMEOUT);&lt;br /&gt;   &lt;br /&gt;  E -&gt;&lt;br /&gt;   error_logger:info_msg("[~p] Received: ~p~n", [?MODULE, E]),&lt;br /&gt;   ssh_loop(CM, Channel, Callback) &lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;ssh_cm sends various message to the calling process, more important tuples are&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;{ssh_cm, CM, {data, _Channel, 0, Data}}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Data holds what you want, and in our case a line sent by the tail process...&lt;br /&gt;The callback defined at the beginning is then executed:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;tail(Host, User, Pass) -&gt;&lt;br /&gt; case ssh_cm:connect(Host, 22, [{user_dir, "/var/tmp/ssh"}, {user, User}, {password, Pass}]) of&lt;br /&gt;  {ok, CM} -&gt;&lt;br /&gt;   session(CM, &lt;br /&gt;    fun(X) -&gt;  % Our Callback &lt;br /&gt;     io:format("-ssh: ~p~n", [X]) % simple display...&lt;br /&gt;    end);&lt;br /&gt;&lt;br /&gt;  Error -&gt;&lt;br /&gt;   Error&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To conclude this simple module is able to spawn a remote "tail -f" using a ssh connection and using a callback function on every data received.&lt;br /&gt;&lt;br /&gt;The code was designed from the ssh_ssh module that you can find in the ssh module source code, because the ssh documentation is really sparse for now...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1780157638906540068?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1780157638906540068/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1780157638906540068' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1780157638906540068'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1780157638906540068'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/11/experimenting-with-erlang-ssh-support.html' title='Experimenting with the erlang SSH support, or remote &apos;tail&apos; with SSH...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6929704955756214195</id><published>2007-11-15T15:47:00.000+01:00</published><updated>2007-11-15T17:02:34.705+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='parallelisation'/><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><category scheme='http://www.blogger.com/atom/ns#' term='regexp'/><title type='text'>An gen_server for mass regexp computing... (LibTre)</title><content type='html'>This is the first test session of my 'tregex_srv' that provides some nice regexp features:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;266&gt; l(tregex_srv).                                                 &lt;br /&gt;{module,tregex_srv}&lt;br /&gt;267&gt; tregex_srv:start_link().                                       &lt;br /&gt;{ok,&lt;0.4045.0&gt;}&lt;br /&gt;268&gt; tregex_srv:store( [&lt;&lt;"[0-9+] pid"&gt;&gt;, &lt;&lt;"[a-z]+.tmp"&gt;&gt;]).       &lt;br /&gt;ok&lt;br /&gt;269&gt; tregex_srv:grep(&lt;&lt;"test 9405904.tmp acuu.tmpmulaor 10+ pid"&gt;&gt;).&lt;br /&gt;[[[{34,39,&lt;&lt;"+ pid"&gt;&gt;}],[{17,25,&lt;&lt;"acuu.tmp"&gt;&gt;}]]]&lt;br /&gt;270&gt; tregex_srv:store( [{ &lt;&lt;"test"&gt;&gt;, fun(X) -&gt; io:format("found: ~p~n", [X]) end},  &lt;&lt;"[0-9][0-9]"&gt;&gt;]).&lt;br /&gt;ok&lt;br /&gt;271&gt; tregex_srv:grep(&lt;&lt;"test 9405904.tmp acuu.tmpmulaor 10+ pid"&gt;&gt;).                                    &lt;br /&gt;found: [{0,4,&lt;&lt;"test"&gt;&gt;}]&lt;br /&gt;[[[{34,39,&lt;&lt;"+ pid"&gt;&gt;}],[{17,25,&lt;&lt;"acuu.tmp"&gt;&gt;}]],&lt;br /&gt; [[{0,4,&lt;&lt;"test"&gt;&gt;}],[{5,7,&lt;&lt;"94"&gt;&gt;}]]]&lt;br /&gt;272&gt; tregex_srv:store( [{ &lt;&lt;"SRC=[^ ]+"&gt;&gt;, fun(X) -&gt; &lt;br /&gt; [{_,_,M}] = X, io:format("Source: ~p~n", [M]) &lt;br /&gt;end}]).                   &lt;br /&gt;ok&lt;br /&gt;273&gt; tregex_srv:grep(&lt;&lt;"test 9405904.tmp acuu.tmpmulaor 10+ pid"&gt;&gt;).                                      &lt;br /&gt;found: [{0,4,&lt;&lt;"test"&gt;&gt;}]&lt;br /&gt;[[[{34,39,&lt;&lt;"+ pid"&gt;&gt;}],[{17,25,&lt;&lt;"acuu.tmp"&gt;&gt;}]],&lt;br /&gt; [[{0,4,&lt;&lt;"test"&gt;&gt;}],[{5,7,&lt;&lt;"94"&gt;&gt;}]],&lt;br /&gt; []]&lt;br /&gt;274&gt; tregex_srv:grep(&lt;&lt;"tst SRC=192.135.15.1 pid"&gt;&gt;).               &lt;br /&gt;Source: &lt;&lt;"SRC=192.135.15.1"&gt;&gt;&lt;br /&gt;[[[{19,24,&lt;&lt;"1 pid"&gt;&gt;}]],&lt;br /&gt; [[{8,10,&lt;&lt;"19"&gt;&gt;}]],&lt;br /&gt; [[{4,20,&lt;&lt;"SRC=192.135.15.1"&gt;&gt;}]]]&lt;br /&gt;275&gt; tregex_srv:store( [{ &lt;&lt;"SRC=([^ ]+)"&gt;&gt;, fun(X) -&gt; &lt;br /&gt; [{_,_,_}, {_,_,M}] = X, io:format("Source IP: ~p~n", [M]) &lt;br /&gt; end}]).&lt;br /&gt;ok&lt;br /&gt;276&gt; tregex_srv:grep(&lt;&lt;"tst SRC=192.135.15.1 pid"&gt;&gt;).                                                                   &lt;br /&gt;Source IP: &lt;&lt;"192.135.15.1"&gt;&gt;&lt;br /&gt;Source: &lt;&lt;"SRC=192.135.15.1"&gt;&gt;&lt;br /&gt;[[[{19,24,&lt;&lt;"1 pid"&gt;&gt;}]],&lt;br /&gt; [[{8,10,&lt;&lt;"19"&gt;&gt;}]],&lt;br /&gt; [[{4,20,&lt;&lt;"SRC=192.135.15.1"&gt;&gt;}]],&lt;br /&gt; [[{4,20,&lt;&lt;"SRC=192.135.15.1"&gt;&gt;},{8,20,&lt;&lt;"192.135.15.1"&gt;&gt;}]]]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see, you can associate Funs with regexp Matches. This means that you can bind action to regexp...&lt;br /&gt;First we store (in fact add regexp to the existing regexp list) new tuples {RE, Fun}:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;275&gt; tregex_srv:store( [{ &lt;&lt;"SRC=([^ ]+)"&gt;&gt;, fun(X) -&gt; &lt;br /&gt; [{_,_,_}, {_,_,M}] = X, io:format("Source IP: ~p~n", [M]) &lt;br /&gt; end}]).&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now the exec does call already registered funs, but call the new one since our regexp matches and you can see that the IP number is only printed, the "submatches" feature works as expected:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;276&gt; tregex_srv:grep(&lt;&lt;"tst SRC=192.135.15.1 pid"&gt;&gt;).                                                                   &lt;br /&gt;Source IP: &lt;&lt;"192.135.15.1"&gt;&gt;&lt;br /&gt;Source: &lt;&lt;"SRC=192.135.15.1"&gt;&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The gen_server state is the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-record(state, {&lt;br /&gt;                requests,&lt;br /&gt;                reindex,&lt;br /&gt;                re = [],&lt;br /&gt;                pids = []&lt;br /&gt;                }).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Its init function is:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;init(_Args) -&gt;&lt;br /&gt;        process_flag(trap_exit, true),&lt;br /&gt;        {ok, #state{  &lt;br /&gt; re = ets:new(?MODULE, [set,private]), &lt;br /&gt; requests = 0, &lt;br /&gt; reindex = 1 }}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Internally the module calls 'treregex:compile' to compile regexp and store the resulting #port into a list that is stored in the 'ets' table. Every call to 'tregex_srv:store' create a new entry in the ets table. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;%% Storing RE and Funs&lt;br /&gt;%% Creating simple fun when there's none provided...&lt;br /&gt;%%&lt;br /&gt;store([], Res, State) -&gt;&lt;br /&gt;        ets:insert(State#state.re, { State#state.reindex, Res});&lt;br /&gt;store([ { Regexp, Fun } | List ], Res, State) -&gt;&lt;br /&gt;        {ok, Re } = treregex:compile(iolist_to_binary(Regexp), [extended]),&lt;br /&gt;        store(List, [ { Re, Fun } | Res ], State);&lt;br /&gt;store([ Regexp | List ], Res, State) -&gt;&lt;br /&gt;        {ok, Re } = treregex:compile(iolist_to_binary(Regexp), [extended]),&lt;br /&gt;        store(List, [ { Re, fun(_) -&gt; false end} | Res ], State).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The 'tregex_srv:grep' just uses 'ets:foldl' to compute results:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;handle_call({grep, Line}, _Node, State) -&gt;&lt;br /&gt;        Requests = State#state.requests,&lt;br /&gt;        Grep = fun({_Reindex, ReList}, Acc) -&gt;&lt;br /&gt;                        [ exec(ReList, Line, []) | Acc]&lt;br /&gt;                end,&lt;br /&gt;        {reply, ets:foldl(Grep, [], State#state.re), State#state{ requests = Requests + 1} }.&lt;br /&gt;&lt;br /&gt;%% exec, using a List of {Re, Funs}&lt;br /&gt;exec([], _Line, Acc) -&gt;&lt;br /&gt;        Acc;&lt;br /&gt;exec([ { Re, Fun } | ReList ], Line, Acc) -&gt;&lt;br /&gt;        case treregex:exec(Re, Line) of&lt;br /&gt;                {ok, Matches} -&gt;&lt;br /&gt;                        Fun(Matches),&lt;br /&gt;                        exec(ReList, Line, [ Matches | Acc ]);&lt;br /&gt;&lt;br /&gt;                {error, nomatch} -&gt;&lt;br /&gt;                        exec(ReList, Line, Acc)&lt;br /&gt;        end;&lt;br /&gt;exec([ _Any | ReList ], Line, Acc) -&gt;&lt;br /&gt;        exec(ReList, Line, Acc).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The code is still young, but seems to work.&lt;br /&gt;&lt;br /&gt;The main purpose here, is to be able to massively process lines of logs. I want to be able to &lt;br /&gt;spawn multiple process on multiples nodes that will be able to extract valuable content from&lt;br /&gt;various lines. This is the first step forward :-)&lt;br /&gt;&lt;br /&gt;I may cleanup the 'grep' fun since it will returns empty list whenever a regexp didn't match anything from the supplied line... &lt;br /&gt;&lt;br /&gt;I'm really excited to think that I will be able to use the 'gen_server:multi_call' with this module :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6929704955756214195?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6929704955756214195/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6929704955756214195' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6929704955756214195'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6929704955756214195'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/11/genserver-for-mass-regexp-computing.html' title='An gen_server for mass regexp computing... (LibTre)'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6753958725425974654</id><published>2007-10-22T12:55:00.000+02:00</published><updated>2007-10-22T12:58:39.626+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><category scheme='http://www.blogger.com/atom/ns#' term='matching'/><title type='text'>LibTRE returning matching values</title><content type='html'>New version of libTRE driver, now exec returns also matching binaries:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [hipe] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.5.5  (abort with ^G)&lt;br /&gt;1&gt; erl_ddll:load_driver(code:priv_dir(treregex)++"/bin", "TRE_drv").&lt;br /&gt;ok&lt;br /&gt;2&gt; {ok, RE} = treregex:compile(&lt;&lt;"([a-z]+)([0-9]+)"&gt;&gt;, [extended]).&lt;br /&gt;{ok,#Port&lt;0.79&gt;}&lt;br /&gt;3&gt; treregex:exec(RE, &lt;&lt;"this is a test9234 of blast"&gt;&gt;). &lt;br /&gt;{ok,[{10,18,&lt;&lt;"test9234"&gt;&gt;},{10,14,&lt;&lt;"test"&gt;&gt;},{14,18,&lt;&lt;"9234"&gt;&gt;}]}&lt;br /&gt;4&gt; &lt;br /&gt;4&gt;  treregex:exec(RE, &lt;&lt;"this is arolpghin39235 test9234 of blast"&gt;&gt;). &lt;br /&gt;{ok,[{8,22,&lt;&lt;"arolpghin39235"&gt;&gt;},{8,17,&lt;&lt;"arolpghin"&gt;&gt;},{17,22,&lt;&lt;"39235"&gt;&gt;}]}&lt;br /&gt;5&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Soon to be released !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6753958725425974654?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6753958725425974654/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6753958725425974654' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6753958725425974654'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6753958725425974654'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/10/libtre-returning-matching-values.html' title='LibTRE returning matching values'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2689731846618771792</id><published>2007-10-17T11:43:00.000+02:00</published><updated>2007-10-17T16:25:35.790+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><category scheme='http://www.blogger.com/atom/ns#' term='approx match'/><title type='text'>LibTRE in Action, the approximative match</title><content type='html'>Here's a sample session showing some of the libTre features:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [hipe] [kernel-poll:false]&lt;br /&gt;&lt;br /&gt;Eshell V5.5.5  (abort with ^G)&lt;br /&gt;1&gt; erl_ddll:load_driver(code:priv_dir(treregex)++"/bin", "TRE_drv").&lt;br /&gt;ok&lt;br /&gt;2&gt; f(RE),  {ok, RE} =  treregex:compile(&lt;&lt;"fear"&gt;&gt;, [extended]).&lt;br /&gt;{ok,#Port&lt;0.74&gt;}&lt;br /&gt;3&gt; treregex:approx(RE, &lt;&lt;"fir"&gt;&gt;, 0, []).&lt;br /&gt;{error,nomatch}&lt;br /&gt;4&gt; treregex:approx(RE, &lt;&lt;"fir"&gt;&gt;, 1, []).&lt;br /&gt;{error,nomatch}&lt;br /&gt;5&gt; treregex:approx(RE, &lt;&lt;"fir"&gt;&gt;, 2, []).&lt;br /&gt;{ok,[{0,3}]}&lt;br /&gt;6&gt; treregex:approx(RE, &lt;&lt;"fir"&gt;&gt;, 1, []).&lt;br /&gt;{error,nomatch}&lt;br /&gt;7&gt; treregex:exec(RE, &lt;&lt;"fir"&gt;&gt;).         &lt;br /&gt;{error,nomatch}&lt;br /&gt;8&gt; treregex:free(RE).&lt;br /&gt;ok&lt;br /&gt;9&gt; f(RE),  {ok, RE} =  treregex:compile(&lt;&lt;"loubov"&gt;&gt;, [extended]).&lt;br /&gt;{ok,#Port&lt;0.76&gt;}&lt;br /&gt;10&gt;  treregex:approx(RE, &lt;&lt;"love"&gt;&gt;, 3, []).&lt;br /&gt;{ok,[{0,3}]}&lt;br /&gt;11&gt;  treregex:approx(RE, &lt;&lt;"to love"&gt;&gt;, 0, []). &lt;br /&gt;{error,nomatch}&lt;br /&gt;12&gt;  treregex:approx(RE, &lt;&lt;"to love"&gt;&gt;, 1, []).&lt;br /&gt;{error,nomatch}&lt;br /&gt;13&gt;  treregex:approx(RE, &lt;&lt;"to love"&gt;&gt;, 2, []).&lt;br /&gt;{error,nomatch}&lt;br /&gt;14&gt;  treregex:approx(RE, &lt;&lt;"to love"&gt;&gt;, 3, []).&lt;br /&gt;{ok,[{0,6}]}&lt;br /&gt;15&gt;  treregex:approx(RE, &lt;&lt;"aimer"&gt;&gt;, 3, []).  &lt;br /&gt;{error,nomatch}&lt;br /&gt;16&gt;  treregex:approx(RE, &lt;&lt;"aimour"&gt;&gt;, 3, []).&lt;br /&gt;{error,nomatch}&lt;br /&gt;17&gt;  treregex:approx(RE, &lt;&lt;"amour"&gt;&gt;, 3, []). &lt;br /&gt;{error,nomatch}&lt;br /&gt;18&gt;  treregex:approx(RE, &lt;&lt;"amour"&gt;&gt;, 10, []).&lt;br /&gt;{ok,[{1,6}]}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2689731846618771792?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2689731846618771792/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2689731846618771792' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2689731846618771792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2689731846618771792'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/10/libtre-in-action-approximative-match.html' title='LibTRE in Action, the approximative match'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2643014166723320084</id><published>2007-10-17T08:32:00.000+02:00</published><updated>2007-10-17T09:12:51.666+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><category scheme='http://www.blogger.com/atom/ns#' term='driver'/><title type='text'>LibTRE, I finally got it working !</title><content type='html'>Hi !&lt;br /&gt;I'm please to say that I finally managed the 'TRE_drv.c' code to work as expected. This time there's no more 'segfault'. The solution is to use only non conflicting function names:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;regncomp and not regcomp&lt;/li&gt;&lt;li&gt;regnexec and not regexec&lt;/li&gt;&lt;li&gt;tree_free and not regfree&lt;/li&gt;&lt;/ul&gt;Since every posix Regex function will be used instead of their Tre equivalent, I was forced do find a special case for 'regfree'. There's no special regX function for 'regfree'.&lt;br /&gt;Because regfree can be called at many places, when an erlang error occurs, or simply when we quit the shell; the function is trying to free the posix regex_t with a pointer to a TRE regex_t, so the crash is always there.&lt;br /&gt;While looking at 'tre-internal.h' I've found the 'tre_free' function that will do the real job of freeing the TRE regex_t... So I've just declared as 'extern' this function directly into the TRE_drv.c file...&lt;pre&gt;extern void tree_free(regex_t *preg);&lt;br /&gt;static void tre_stop(ErlDrvData drv_data)&lt;br /&gt;{&lt;br /&gt;    struct driver *d = (struct driver*) drv_data;&lt;br /&gt;&lt;br /&gt;//    if (d-&gt;compiled)&lt;br /&gt;//      regfree(&amp;(d-&gt;re));&lt;br /&gt;&lt;br /&gt;        if (d-&gt;compiled)&lt;br /&gt;                tre_free(&amp;d-&gt;re);&lt;br /&gt;&lt;br /&gt;    driver_free(d);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This time I've also added a call to the 'reganexec' function that's able to find approximate matches. The code is of course located into the 'tre_from_erlang' function. (this function gets called whenever erlang talks to the driver).&lt;pre&gt;&lt;br /&gt;... snip ...&lt;br /&gt;                case APPROX: &lt;br /&gt;                        &lt;br /&gt;                        /* &lt;&lt;op:32, flags:32, cost:32, String/Binary&gt;&gt; */&lt;br /&gt;&lt;br /&gt;                        flags = get_int32(buf + 4);&lt;br /&gt;                        cost = get_int32(buf + 8);&lt;br /&gt;&lt;br /&gt;                        memset(&amp;amatches, 0, sizeof(amatches));&lt;br /&gt;                        amatches.pmatch = matches;&lt;br /&gt;                        amatches.nmatch = MAX_MATCHES;&lt;br /&gt;&lt;br /&gt;                        regaparams_default(&amp;regaparams);&lt;br /&gt;                        regaparams.max_cost = cost;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;                        status = reganexec(&amp;d-&gt;re, buf + 12,  len - 12, &amp;amatches, regaparams,  flags);&lt;br /&gt;                        if (status != 0) {&lt;br /&gt;                                driver_send_status(d, status);&lt;br /&gt;                                return;&lt;br /&gt;                        }&lt;br /&gt;&lt;br /&gt;                        driver_send_matches(d, matches, MAX_MATCHES);&lt;br /&gt;                        break;&lt;br /&gt;... snip ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With this you're able to make things like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;{ok, RE} = treregex:compile(&lt;&lt;"test"&gt;&gt;, [extended]).&lt;br /&gt;treregex:approx(RE, &lt;&lt;"this is a tast"&gt;&gt;, 0, []).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;the 'fun approx/4' has the 'cost' (an integer) as extra argument, this correspond to the cost of manipulation characters to find a match. You'll find more about this &lt;a href="http://laurikari.net/tre/"&gt;here&lt;/a&gt;.&lt;pre&gt;&lt;br /&gt;Approximate pattern matching allows matches to be approximate, that is, allows the matches to be &lt;br /&gt;close to the searched pattern under some measure of closeness. TRE uses the edit-distance measure&lt;br /&gt; (also known as the Levenshtein distance) where characters can be inserted, deleted, or &lt;br /&gt;substituted in the searched text in order to get an exact match. Each insertion, deletion, or &lt;br /&gt;substitution adds the distance, or cost, of the match. TRE can report the matches which have a &lt;br /&gt;cost lower than some given threshold value. TRE can also be used to search for matches with the &lt;br /&gt;lowest cost.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally, now that the driver and the erlang module works well I'll upload it to various repositories...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2643014166723320084?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2643014166723320084/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2643014166723320084' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2643014166723320084'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2643014166723320084'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/10/libtre-i-finally-got-it-working.html' title='LibTRE, I finally got it working !'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1591954673821510048</id><published>2007-10-13T19:54:00.000+02:00</published><updated>2007-10-13T20:18:22.234+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='segfault'/><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><category scheme='http://www.blogger.com/atom/ns#' term='debugging'/><title type='text'>LibTre and Posix, the segfault explanation</title><content type='html'>You already know that I want to make a libTRE driver such as the RE posix driver... But what you may not know is that I've never lost this amount of time debugging things... &lt;br /&gt;I've, for a long long time ago, called 'gdb' my friend to rescue me ... &lt;br /&gt;Let me explain the problem: we want to call 'regcomp' and 'regexec' from the libTRE package. Take your time and reread this sentence:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;We want to call 'regcomp' and 'regexec' from the libTRE package.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This seems really easy, but we want to make this from a shared library... A shared library that we will open at runtime. &lt;br /&gt;And lauching our erlang shell make the ld process finds symbols addresses in various .so files for us automagically...&lt;br /&gt;&lt;br /&gt;Since I'm using ubuntu, the posix regexp are located in the libc.so.6 library, so ld knows about regcomp and regexec before I can even call 'erl_ddll:load_driver/2'...&lt;br /&gt;&lt;br /&gt;Loading the driver into the erlang vm from my freshly built .so file, don't really work as expected... 'regcomp' is still the one from the glibc, and not the one from the libTRE driver...&lt;br /&gt;&lt;br /&gt;But 'regex_t' is the one from tre, and 'regcomp' is the one from posix... There definition of the regex_t struct isn't the same :p&lt;br /&gt;&lt;br /&gt;The driver structure:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;typedef struct _desc {&lt;br /&gt;    ErlDrvPort     port;&lt;br /&gt;    ErlDrvTermData dport;          /* the port identifier as ErlDrvTermData */&lt;br /&gt;    regex_t re;&lt;br /&gt;    regmatch_t pm[16];&lt;br /&gt;    int compiled;&lt;br /&gt;} Desc;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now the 'sizeof(regex_t)' from Tre is 8 and the posix is a lot more ... So when I call the 'regcomp' function like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;        switch(op) {&lt;br /&gt;                case COMPILE: &lt;br /&gt;... snip ...&lt;br /&gt;                        status = regcomp(&amp;d-&gt;re, buf+8, flags);&lt;br /&gt;                        d-&gt;compiled = 1;&lt;br /&gt;... snip ...&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The 'regmatch_t' is exactly overflowed by exactly &lt;pre&gt;( sizeof (posix regex_t) - (2 * sizeof (tre regex_t)) ) &lt;/pre&gt;bytes ...&lt;br /&gt;&lt;br /&gt;This is the reason why I get a segfault (from my last post) when I try to call 'regexec'...&lt;br /&gt;&lt;br /&gt;I'm now rewriting a simpler interface to libtre that will not have names that can collide with posix...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1591954673821510048?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1591954673821510048/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1591954673821510048' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1591954673821510048'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1591954673821510048'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/10/libtre-and-posix-segfault-explanation.html' title='LibTre and Posix, the segfault explanation'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6873983652697459969</id><published>2007-10-10T12:19:00.001+02:00</published><updated>2007-10-10T22:41:10.047+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='libtre'/><category scheme='http://www.blogger.com/atom/ns#' term='regexp'/><title type='text'>LibTre for Fast regular expression</title><content type='html'>While reading the almost &lt;a href="http://swtch.com/~rsc/regexp/regexp1.html"&gt;famous article about regular expressions&lt;/a&gt;, I tried to use &lt;a href="http://laurikari.net/tre/"&gt;TRE&lt;/a&gt;.&lt;br /&gt;Since TRE is posix Compliant, but unfortunately don't have any erlang driver I downloaded ''posregex'' and hack it a little to make it use TRE.&lt;br /&gt;&lt;br /&gt;Now here's some experiment with it:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;22&gt; Init = fun() -&gt;  erl_ddll:load_driver(code:priv_dir(treregex)++"/bin", "TRE_drv") end.&lt;br /&gt;#Fun&lt;erl_eval.20.112921583&gt;&lt;br /&gt;23&gt; Init().&lt;br /&gt;ok&lt;br /&gt;24&gt; f(List), List = treutils:build([&lt;&lt;"test"&gt;&gt;, &lt;&lt;"toto"&gt;&gt;, &lt;&lt;"[a-z][0-9]$"&gt;&gt;, &lt;&lt;"^[a-zA-Z][a-zA-Z0-9_]+"&gt;&gt;]).&lt;br /&gt;[{&lt;&lt;"test"&gt;&gt;,#Port&lt;0.84&gt;},&lt;br /&gt;{&lt;&lt;"toto"&gt;&gt;,#Port&lt;0.85&gt;},&lt;br /&gt;{&lt;&lt;"[a-z][0-9]$"&gt;&gt;,#Port&lt;0.86&gt;},&lt;br /&gt;{&lt;&lt;"^[a-zA-Z][a-zA-Z0-9_]+"&gt;&gt;,#Port&lt;0.87&gt;}]&lt;br /&gt;&lt;br /&gt;25&gt; treutils:exec(List, &lt;&lt;"alkjlskdjflskjglksjflakgjlkfgjl;dkgjklsdjglkdsjfglksd&lt;br /&gt;jlkgjsdlkfgjlsdk fg989t9sgdkgj lkyrdy sjd gyrdsl;gkj test asl;dksdf"&gt;&gt;).&lt;br /&gt;{&lt;&lt;"alkjlskdjflskjglksjflakgjlkfgjl;dkgjklsdjglkdsjfglksdjlkgjsdlkfgjlsdk &lt;br /&gt;fg989t9sgdkgj lkyrdy sjd gyrdsl;gkj test a"...&gt;&gt;,&lt;br /&gt;&lt;br /&gt;[{ok,&lt;&lt;"^[a-zA-Z][a-zA-Z0-9_]+"&gt;&gt;},{ok,&lt;&lt;"test"&gt;&gt;}]}&lt;br /&gt;&lt;br /&gt;26&gt; treutils:exec(List, &lt;&lt;"10alkjlskdjflskjglksjflakgjlkfgjl;dkgjklsdjglkdsjfglksdjlkgjsdlkfgjlsdk &lt;br /&gt;fg989t9sgdkgj lkyrdy sjd gyrdsl;gkj test asl;dksdf"&gt;&gt;).                   &lt;br /&gt;{&lt;&lt;"10alkjlskdjflskjglksjflakgjlkfgjl;dkgjklsdjglkdsjfglksdjlkgjsdlkfgjlsdk &lt;br /&gt;fg989t9sgdkgj lkyrdy sjd gyrdsl;gkj test"...&gt;&gt;,&lt;br /&gt;&lt;br /&gt;[{ok,&lt;&lt;"test"&gt;&gt;}]}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I build a List of Port that are compiled regular expressions, then I iterate thru the list matching "Line".&lt;br /&gt;&lt;br /&gt;Here's the TreUtils module (BTW, you can replace treregex with posregex if you want...)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(treutils).&lt;br /&gt;-export([build/1, exec/2, destroy/1]).&lt;br /&gt;&lt;br /&gt;build(List) -&gt;&lt;br /&gt;       Fun = fun(X) -&gt;&lt;br /&gt;               {ok, RE} = treregex:compile(X, [extended]),&lt;br /&gt;               {X, RE}&lt;br /&gt;       end,&lt;br /&gt;       lists:map(Fun, List).&lt;br /&gt;&lt;br /&gt;destroy(List) -&gt;&lt;br /&gt;       lists:foreach( fun({_Name, RE}) -&gt; treregex:free(RE) end, List).&lt;br /&gt;&lt;br /&gt;exec(List, Line) -&gt;&lt;br /&gt;       exec(List, Line, []).&lt;br /&gt;&lt;br /&gt;exec([], Line, Acc) -&gt;&lt;br /&gt;       {Line, Acc};&lt;br /&gt;exec([H|Rest], Line, Acc) -&gt;&lt;br /&gt;       {Name, RE} = H,&lt;br /&gt;       case treregex:match(RE, Line) of&lt;br /&gt;               ok -&gt;&lt;br /&gt;                       exec(Rest, Line, [ {ok, Name} | Acc ]);&lt;br /&gt;&lt;br /&gt;               {error, nomatch} -&gt;&lt;br /&gt;                       exec(Rest, Line, Acc)&lt;br /&gt;       end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Other &lt;a href="http://swtch.com/%7Ersc/regexp/"&gt;libraries&lt;/a&gt; are also available.&lt;br /&gt;&lt;br /&gt;Unfortunately I'm unable to make this module rock solid since it segfault if I ever call the 'exec' method two times... I think that there's a problem in the TRV_drv.c (heavily copied from the RE_drv.c) in the 'RE_from_erlang' function. The regexec call may garbage some of its internal...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;typedef struct _desc {&lt;br /&gt;    ErlDrvPort     port;&lt;br /&gt;    ErlDrvTermData dport;          /* the port identifier as ErlDrvTermData */&lt;br /&gt;    regex_t re;&lt;br /&gt;    regmatch_t pm[16];&lt;br /&gt;    int compiled;&lt;br /&gt;} Desc;&lt;br /&gt;&lt;br /&gt;... snip ...&lt;br /&gt;&lt;br /&gt;static void RE_from_erlang(ErlDrvData drv_data, char *buf, int len)&lt;br /&gt;{&lt;br /&gt;        int status;&lt;br /&gt;        unsigned int op = get_int32(buf);&lt;br /&gt;        unsigned int flags = get_int32(buf+4);&lt;br /&gt;        Desc *d = (Desc*) drv_data;&lt;br /&gt;&lt;br /&gt;        switch(op) {&lt;br /&gt;&lt;br /&gt;... snip ...&lt;br /&gt;&lt;br /&gt;                case EXEC: &lt;br /&gt;                        status = regexec(&amp;d-&gt;re, buf+8, (size_t) 16, &amp;d-&gt;pm[0], flags);&lt;br /&gt;                        if (status != 0) {&lt;br /&gt;                                driver_send_status(d, status);&lt;br /&gt;                                return;&lt;br /&gt;                        }&lt;br /&gt;                        driver_send_pm(d);&lt;br /&gt;                        break;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I hope I'll come back soon with good news, since this TRE library looks very very promising...&lt;br /&gt;If anyone have any clue ;p, please comment !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6873983652697459969?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6873983652697459969/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6873983652697459969' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6873983652697459969'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6873983652697459969'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/10/libtre-for-fast-regular-expression.html' title='LibTre for Fast regular expression'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1485962324202967853</id><published>2007-10-02T08:55:00.000+02:00</published><updated>2007-10-02T09:00:13.571+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='high order functions'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><title type='text'>High Order Functions must be tested before use</title><content type='html'>From my previous article comments it is a small and efficient method of filtering datas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Cpu = fun({cpu, _, _}) -&gt; true; (_) false end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But whenever we want to transposing it into a HOF (high order function):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Filter = fun(Elem) -&gt;&lt;br /&gt; fun({Elem, _, _}) -&gt; true; (_) false end&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This naive approach doesn't work:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;1&gt; Filter = fun(Elem) -&gt; fun({Elem, _, _}) -&gt; true; (_) -&gt; false end end. &lt;br /&gt;#Fun&amp;lt;erl_eval.6.49591080&gt;&lt;br /&gt;2&gt; C = Filter(cpu).&lt;br /&gt;#Fun&amp;lt;erl_eval.6.49591080&gt;&lt;br /&gt;3&gt; C({test, t, t}).&lt;br /&gt;true % this should have been false ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As explained in &lt;a href="http://www.erlang.org/doc/programming_examples/funs.html#2.3"&gt;this document&lt;/a&gt;, we need to use guards to make our high order function effective:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;The rules for importing variables into a fun has the consequence that certain pattern matching &lt;br /&gt;operations have to be moved into guard expressions and cannot be written in the head of the fun. &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The correct way is:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Filter = fun(Elem) -&gt;&lt;br /&gt; fun({X, _, _}) when X == Elem -&gt; true; (_) false end&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So we must keep in mind that sometimes we should really check that our HOF is working as expected !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1485962324202967853?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1485962324202967853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1485962324202967853' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1485962324202967853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1485962324202967853'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/10/high-order-functions-must-be-tested.html' title='High Order Functions must be tested before use'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3824069361520576304</id><published>2007-10-01T09:49:00.000+02:00</published><updated>2007-10-02T08:29:24.909+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='statistics'/><category scheme='http://www.blogger.com/atom/ns#' term='high order functions'/><title type='text'>High Order Functions, filtering lists...</title><content type='html'>I have a list of collected cpu and network values, from eth0 and eth1:&lt;br /&gt;&lt;pre&gt; L =&lt;br /&gt;[{cpu,user,&amp;lt;&amp;lt;"3.05"&amp;gt;&amp;gt;},&lt;br /&gt; {cpu,nice,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {cpu,system,&amp;lt;&amp;lt;"0.72"&amp;gt;&amp;gt;},&lt;br /&gt; {cpu,iowait,&amp;lt;&amp;lt;"0.03"&amp;gt;&amp;gt;},&lt;br /&gt; {cpu,steal,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {cpu,idle,&amp;lt;&amp;lt;"96.20"&amp;gt;&amp;gt;},&lt;br /&gt; {eth0,rxpck,&amp;lt;&amp;lt;"2.52"&amp;gt;&amp;gt;},&lt;br /&gt; {eth0,txpck,&amp;lt;&amp;lt;"0.15"&amp;gt;&amp;gt;},&lt;br /&gt; {eth0,rxbyt,&amp;lt;&amp;lt;"173.80"&amp;gt;&amp;gt;},&lt;br /&gt; {eth0,txbyt,&amp;lt;&amp;lt;"44.68"&amp;gt;&amp;gt;},&lt;br /&gt; {eth0,rxcmp,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {eth0,txcmp,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {eth0,rxmcst,&amp;lt;&amp;lt;"1.25"&amp;gt;&amp;gt;},&lt;br /&gt; {eth1,rxpck,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {eth1,txpck,&amp;lt;&amp;lt;"0.02"&amp;gt;&amp;gt;},&lt;br /&gt; {eth1,rxbyt,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {eth1,txbyt,&amp;lt;&amp;lt;"1.00"&amp;gt;&amp;gt;},&lt;br /&gt; {eth1,rxcmp,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {eth1,txcmp,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;},&lt;br /&gt; {eth1,rxmcst,&amp;lt;&amp;lt;"0.00"&amp;gt;&amp;gt;}]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If I want to manipulate such data set I'll need some filter functions that will help me to extract values, I need cpu values and eth0 values. High order function can do that for me !&lt;br /&gt;&lt;br /&gt;First we need to select tuples, 'cpu' tuples:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Cpu = fun({X, Y, Z}) -&gt; &lt;br /&gt; if X == cpu -&gt; &lt;br /&gt;  true; &lt;br /&gt; true -&gt;&lt;br /&gt;  false &lt;br /&gt; end &lt;br /&gt;end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A better and far more erlangish method (thanks Zvi):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Cpu = fun({cpu,_,_}) -&gt; true;&lt;br /&gt;         (_) -&gt; false&lt;br /&gt;end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Okay this fun will return true whenever the first element of the tuple is 'cpu'.&lt;br /&gt;But this fun is static, since 'cpu' is written in the function body. Let's make it dynamic:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Filter = fun(Motif) -&gt;&lt;br /&gt;  fun({X, Y, Z}) -&gt;&lt;br /&gt;   if X == Motif -&gt;&lt;br /&gt;    true;&lt;br /&gt;   true -&gt;&lt;br /&gt;    false &lt;br /&gt;   end&lt;br /&gt;  end &lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The new High order function 'Filter' is generated by 'fun(Motif)' and takes as argument a tuple '{X, Y, Z}', this is a fun that return a fun...&lt;br /&gt;This function can be used like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;List = {cpu, test, dummy}. % a sample list&lt;br /&gt;Cpu = Filter(cpu). % generate the Cpu fun&lt;br /&gt;Cpu(List). %executing the Cpu fun&lt;br /&gt;true.&lt;br /&gt;List2 = {test, cpu, dummy}. %Other dummy list&lt;br /&gt;Cpu(List2).&lt;br /&gt;false.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Come back to our initial data set, and realize that we must iterate thru the list to extract possible values. Iterate and apply a fun to every element is what we need to do, futhermore we need to retrieve matching values... In fact we need to build the list of extracted values, and this can be accomplished by 'lists:foldl':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;extractor(Motif) -&gt;&lt;br /&gt; fun(L) -&gt; &lt;br /&gt;  lists:foldl(&lt;br /&gt;   fun({X, Y, Z}, List) -&gt; &lt;br /&gt;    if X == Motif &lt;br /&gt;     -&gt; [ {Y,Z} | List ]; &lt;br /&gt;     true -&gt; List &lt;br /&gt;    end &lt;br /&gt;   end, &lt;br /&gt;   [], L) &lt;br /&gt; end. &lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;In details, 'List' is the accumulator list, the one that will grow with valid tuple, the one we will return. The fun is the same as described before. To make things clear 'extractor/1' returns a fun that will parse a list of tuple extracting values that matches 'Motif'.&lt;br /&gt;&lt;br /&gt;Another easier method, using only list comprehension:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;extractor(Motif) -&gt;&lt;br /&gt;        fun(L) when is_list(L) -&gt;&lt;br /&gt;                [ {Y, Z} || {X, Y, Z} &lt;- L, X == Motif]&lt;br /&gt;        end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Usage:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;38&gt; Cpu = module:extractor(cpu).&lt;br /&gt;#Fun&amp;lt;module.1.131615259&gt;&lt;br /&gt;39&gt; Cpu(L).&lt;br /&gt;[{idle,&lt;&lt;"96.20"&gt;&gt;},&lt;br /&gt; {steal,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {iowait,&lt;&lt;"0.03"&gt;&gt;},&lt;br /&gt; {system,&lt;&lt;"0.72"&gt;&gt;},&lt;br /&gt; {nice,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {user,&lt;&lt;"3.05"&gt;&gt;}]&lt;br /&gt;40&gt; Eth0 = module:extractor(eth0).&lt;br /&gt;41&gt; Eth0(L).&lt;br /&gt;[{rxmcst,&lt;&lt;"1.25"&gt;&gt;},&lt;br /&gt; {txcmp,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {rxcmp,&lt;&lt;"0.00"&gt;&gt;},&lt;br /&gt; {txbyt,&lt;&lt;"44.68"&gt;&gt;},&lt;br /&gt; {rxbyt,&lt;&lt;"173.80"&gt;&gt;},&lt;br /&gt; {txpck,&lt;&lt;"0.15"&gt;&gt;},&lt;br /&gt; {rxpck,&lt;&lt;"2.52"&gt;&gt;}]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I use such code for my monitoring project, I use the sar output (sysstat package), and I graph using rrdtool... This is a part of a bigger project that will compose a 'scheduler'.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3824069361520576304?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3824069361520576304/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3824069361520576304' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3824069361520576304'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3824069361520576304'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/10/high-order-functions-filtering-lists.html' title='High Order Functions, filtering lists...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1542122708973712791</id><published>2007-09-29T17:02:00.000+02:00</published><updated>2007-09-29T17:43:20.707+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='high order functions'/><title type='text'>Building Things with high order functions...</title><content type='html'>One thing that always surprise me, is code that extensively uses high order function or let's call that "functions that returns functions" (may be known also as "closure")...&lt;br /&gt;What can I, myself, do with such things ?&lt;br /&gt;After a little time reading and thinking, and reading and reading, I designed a really simple usage of all of this. I'll create some helper functions to build xml tags...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(tags).&lt;br /&gt;-export([tags/1]).&lt;br /&gt;&lt;br /&gt;tags(Elem) -&amp;gt;&lt;br /&gt;       fun(X) -&amp;gt;&lt;br /&gt;               "&amp;lt;" ++ Elem ++ "&amp;gt;" ++ X ++ "&amp;lt;/" ++ Elem ++ "&amp;gt;"&lt;br /&gt;       end.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let me explain:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Elem will be the enclosing tag&lt;/li&gt;&lt;li&gt;X is the parameter the function will receive at call time&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The module in action:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;1&gt; c(tags).&lt;br /&gt;{ok,tags}&lt;br /&gt;2&gt; Div = tags:tags("div").&lt;br /&gt;#Fun&amp;lt;tags.0.28130594&amp;gt;&lt;br /&gt;3&gt; Div("html text").&lt;br /&gt;"&amp;lt;div&amp;gt;html text&amp;lt;/div&amp;gt;"&lt;br /&gt;4&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;'tags:tags' returns a function that will create div tags...&lt;br /&gt;&lt;br /&gt;Now you're able to build a list of functions that will create valid output, without knowing the real syntax. You can see 'tags:tags' as a function that abstract the final notation of an element of your choice.&lt;br /&gt;&lt;br /&gt;For example, building a 'title' tags is done by : &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Title = tags:tags("title").&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Building a list of functions for your language can be done like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;lists:map(fun tags:tags/1, ["title", "div", "p", "ul", "li", "script"]).&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1542122708973712791?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1542122708973712791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1542122708973712791' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1542122708973712791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1542122708973712791'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/09/building-things-with-high-order.html' title='Building Things with high order functions...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-5359519119136604366</id><published>2007-09-11T09:53:00.000+02:00</published><updated>2007-10-02T22:04:15.629+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Authentication'/><category scheme='http://www.blogger.com/atom/ns#' term='ExtensibleMatch'/><category scheme='http://www.blogger.com/atom/ns#' term='Ldap'/><category scheme='http://www.blogger.com/atom/ns#' term='Active Directory'/><title type='text'>Erlang LDAP support and Active Directory</title><content type='html'>Where I work now, LDAP and Active Directory are used everywhere, since this is a nice idea and everybody is used to it, I have the obligation to use the same framework for users authentication...&lt;br /&gt;Once there's a central authentication mecanism somewhere it would be idiotic to not use it :p&lt;br /&gt;&lt;br /&gt;This is why I started looking the Erlang LDAP support... &lt;br /&gt;&lt;br /&gt;Everybody knows that erlang is really good at doing ASN1, but more people may not know that this feature is very very powerful... And while using the LDAP protocol this is really a killer feature, because once all ELDAP.* generated files are written you have access to the full power of LDAPv3...&lt;br /&gt;&lt;br /&gt;I was idling a little on #erlang and talked a bit about LDAP and finally 'etnt' shows me the 'eldap' module... The first article I found was the one on &lt;a href="http://www.trapexit.org/How_to_talk_LDAP_from_Erlang"&gt;Trapexit.org&lt;/a&gt;.&lt;br /&gt;Since I use &lt;a href="http://cean.process-one.net/"&gt;CEAN&lt;/a&gt; to work I just need to do &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;cean:install(eldap).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To get the 'eldap' module installed, I also use the developer cean package so I can retrieve source files aka *.erl files...&lt;br /&gt;&lt;br /&gt;Then comes problems...&lt;br /&gt;I wasn't able to connect to any of both servers. I was able to see that the tcp connection was correct but that the module don't want to bind (LDAP meaning).&lt;br /&gt;&lt;br /&gt;I then activate the debug level (really nice feature):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;eldap:debug_level("ldap", 2).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;After that everything becomes clearer... The 'check_Pkt/1' fun incorrectly drop the packet !&lt;br /&gt;I modified the fun, and make it returns 'ok'. Right after that every connection (bind) attempt worked flawlessly...&lt;br /&gt;&lt;br /&gt;Another thing I need to do was searching DN from a DN attributes, this cannot be done by anything else than a '&lt;a href="http://www.alvestrand.no/objectid/2.5.13.1.html"&gt;distinguishedNameMatch&lt;/a&gt;' only available with an 'ExtensibleMatch':&lt;br /&gt;&lt;br /&gt;Performing ExtensibleMatch (LDAPv3):&lt;br /&gt;We need to add this line into the 'v_filter/1' fun:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;v_filter({extensibleMatch, AV}) -&gt; {extensibleMatch, AV};&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The rest is fully handled by the ASN1 part...&lt;br /&gt;&lt;br /&gt;Making an ExtensibleMatch part 2:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Base = "dc=example,dc=com".&lt;br /&gt;Filter = {extensibleMatch, {'MatchingRuleAssertion',"2.5.13.1", "CN", "Username", false}}&lt;br /&gt;eldap:search("ldap", [ Base, Filter ]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;While this query perfectly works with OpenLdap it doesn't with Active Directory !&lt;br /&gt;In fact AD closes the connection directly... (May be a bug ?)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-5359519119136604366?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/5359519119136604366/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=5359519119136604366' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5359519119136604366'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/5359519119136604366'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/09/erlang-ldap-support-and-active.html' title='Erlang LDAP support and Active Directory'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2116759427809366184</id><published>2007-08-15T00:37:00.000+02:00</published><updated>2007-08-15T00:57:48.114+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='http'/><category scheme='http://www.blogger.com/atom/ns#' term='picasa'/><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='googerl'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><title type='text'>Erlang Picasa API, it works, and here's some reason why...</title><content type='html'>The Post request to the picasa web service:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;request_picasa(AuthToken, User, ImgName, Data) -&gt;&lt;br /&gt;     Body = iolist_to_binary(Data),&lt;br /&gt;     Authorization = &amp;lt;&amp;lt;"GoogleLogin auth=", AuthToken/binary&gt;&gt;,&lt;br /&gt;     Url = "http://picasaweb.google.com/data/feed/api/user/" ++ User ++ "/album/blog",&lt;br /&gt;     case http:request(post,&lt;br /&gt;                     {       Url,&lt;br /&gt;                             [ { "Authorization", binary_to_list(Authorization) },&lt;br /&gt;                               { "Slug", ImgName }&lt;br /&gt;                             ],&lt;br /&gt;                             "image/jpeg",                           % Content-type&lt;br /&gt;                             Body                                    % Body&lt;br /&gt;                     },&lt;br /&gt;             [ {timeout, 30000}, {sync, false} ],            % HTTPOptions&lt;br /&gt;             [ {body_format, binary} ]) of                   % Options&lt;br /&gt;&lt;br /&gt;             {ok, Result} -&gt;&lt;br /&gt;                     case Result of&lt;br /&gt;                             {{_, 201, _}, Headers, Response} -&gt;&lt;br /&gt;                                     {ok, Headers, Response};&lt;br /&gt;&lt;br /&gt;                             {_,_,Response} -&gt;&lt;br /&gt;                                     Response&lt;br /&gt;                     end;&lt;br /&gt;&lt;br /&gt;             {error, Reason} -&gt;&lt;br /&gt;                     io:format("Error: ~p~n", [Reason])&lt;br /&gt;     end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And what's you must note is the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;a new url to Post your image.&lt;/li&gt;&lt;li&gt;a new header named 'Slug' holding the value of the filename you want for your image&lt;/li&gt;&lt;li&gt;a timeout of 30 seconds, letting enough time for the upload process to complete.&lt;/li&gt;&lt;/ul&gt;Other headers are mandatory, and hopefully already set by 'http:request':&lt;br /&gt;&lt;ul&gt;&lt;li&gt;content-length holding the size of the binary data of your image&lt;/li&gt;&lt;li&gt;content-type holding the image type, here 'image/jpeg'&lt;/li&gt;&lt;/ul&gt;I choose to use the 'prim_file:load_file/1' fun to retrieve a binary stream of octet composing the image I want to upload:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;      {ok, File} = prim_file:read_file(Image),&lt;br /&gt;      {value, {_, Auth}} = lists:keysearch(picasa, 1, State#state.auth),&lt;br /&gt;      case request_picasa(Auth, User, ImgName, File ) of&lt;br /&gt;&lt;/pre&gt;The variable File holds the binary data, the Auth variable holds the 'correct' Picasa AuthToken, so the 'request_picasa/4' fun can be called correctly...&lt;br /&gt;&lt;br /&gt;I'll upgrade later the project &lt;a href="http://code.google.com/p/googerl"&gt;http://code.google.com/p/googerl&lt;/a&gt;, because I want the 'request_picasa' fun to return {ok, ImageUrl}, and to do that I need to parse the atom response sent by Google... And I'm asking myself wether I doit using xmerl or leex ;]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2116759427809366184?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2116759427809366184/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2116759427809366184' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2116759427809366184'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2116759427809366184'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/erlang-picasa-api-it-works-and-heres.html' title='Erlang Picasa API, it works, and here&apos;s some reason why...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2376469417510858962</id><published>2007-08-14T00:32:00.000+02:00</published><updated>2007-08-14T00:42:42.674+02:00</updated><title type='text'>Picasa API for erlang, within the googerl project...</title><content type='html'>I'm working on the Picasa module for googerl, and was unable to get something else than:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Token Invalid&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;While scouting my network traffic to really see what I sent:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;POST /data/feed/api/user/XXXXXXX/album/blog HTTP/1.1.&lt;br /&gt;content-type: image/jpeg.&lt;br /&gt;content-length: 24425.&lt;br /&gt;te: .&lt;br /&gt;host: picasaweb.google.com.&lt;br /&gt;authorization: GoogleLogin auth=DQAAAIAAAAA5xws0PFfzZEzJ9JsboXhEtKXCdV06ZobagrB61zPWy9lU4j9dOQEK247yR8aQMS83mr3AmPPjkGLTnk3aGUjWsaYmUjmyw20gWrgrZUMcLijbRJUyg5J_t1x45qbglqn7Z07NtIHXudh8GpmnWkTE9T-sghc7AokEiDMzOFmGRg.&lt;br /&gt;connection: keep-alive.&lt;br /&gt;slug: test-v.jpg.&lt;br /&gt;.&lt;br /&gt;......JFIF.....H.H......Exif..MM.*.....................&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I realize that my post was correct !&lt;br /&gt;So why this error ?&lt;br /&gt;&lt;br /&gt;The answer is in the Picasa Documentation, and it's hard to find but here it is:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Include the relevant parameters in the body of the POST request, as described in the ClientLogin documentation. Use lh2 as the service name.&lt;br /&gt;&lt;/blockquote&gt; (extracted from http://code.google.com/apis/picasaweb/gdata.html#Add_Album_Manual_Installed)&lt;br /&gt;I must retrieve a token for the 'lh2' service ! The one I've sent was for the 'blogger' service.&lt;br /&gt;&lt;br /&gt;So there's a problem with my current implementation, since my 'google.erl' module is only storing one AuthToken... I'll have to fix this, I'll use a list of AuthToken tuple:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;[ {picasa, "authtoken"}, {blogger, "authtoken"} ]&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;So the code will be heavily modified, and for the time being doesn't work anymore :p&lt;br /&gt;I'll come back soon with good news for everyone !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2376469417510858962?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2376469417510858962/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2376469417510858962' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2376469417510858962'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2376469417510858962'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/picasa-api-for-erlang-within-googerl.html' title='Picasa API for erlang, within the googerl project...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1322795818093608258</id><published>2007-08-11T17:40:00.000+02:00</published><updated>2007-08-14T00:45:50.048+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='trick'/><category scheme='http://www.blogger.com/atom/ns#' term='lists'/><title type='text'>Managing lists of binaries or strings...</title><content type='html'>When you want to deal with binaries, the preserve speed and 'normal' memory footprint there's one fun that you must be aware !&lt;br /&gt;&lt;br /&gt;This is 'iolist_to_binary' and it's brother 'iolist_size'.&lt;br /&gt;&lt;br /&gt;With theses you can do whatever you need to manager large array of strings, or manage small strings or small binaries... In fact you can do everything.&lt;br /&gt;&lt;br /&gt;From my blogger module I need a fun to give me back only binary stuff, for example this fun that just returns a valid 'div' element:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;test(Title) -&gt;&lt;br /&gt;       [ &amp;lt;&amp;lt;"&amp;lt;div class='title'&gt;"&gt;&gt;, iolist_to_binary(Title), &amp;lt;&amp;lt;"&amp;lt;/div"&gt;&gt; ].&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With the 'iolist_to_binary' fun I don't have to deal with guards... ie: I don't need to call 'list_to_binary' if the argument is a list...&lt;br /&gt;Here's some tests:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;35&gt; blogger_utils:test("blah"). %this is a string&lt;br /&gt;[&amp;lt;&amp;lt;'"&amp;lt;div class='title'&gt;"&gt;&gt;,&amp;lt;&amp;lt;"blah"&gt;&gt;,&amp;lt;&amp;lt;"&amp;lt;/div"&gt;&gt;]&lt;br /&gt;&lt;br /&gt;36&gt; blogger_utils:test(&lt;&lt;"blah"&gt;&gt;). %this is a binary&lt;br /&gt;[&amp;lt;&amp;lt;"&amp;lt;div class='title'&gt;"&gt;&gt;,&amp;lt;&amp;lt;"blah"&gt;&gt;,&amp;lt;&amp;lt;"&amp;lt;/div"&gt;&gt;]&lt;br /&gt;&lt;br /&gt;37&gt; blogger_utils:test([&lt;&lt;"blah"&gt;&gt;, "bli"]). %this is a list of mixed types&lt;br /&gt;[&amp;lt;&amp;lt;"&amp;lt;div class='title'&gt;"&gt;&gt;,&amp;lt;&amp;lt;"blahbli"&gt;&gt;,&amp;lt;&amp;lt;"&amp;lt;/div"&gt;&gt;]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Convenient isn't it ?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1322795818093608258?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1322795818093608258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1322795818093608258' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1322795818093608258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1322795818093608258'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/managing-lists-of-binaries-or-strings.html' title='Managing lists of binaries or strings...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-7048917043928101011</id><published>2007-08-07T22:11:00.000+02:00</published><updated>2007-08-07T22:23:52.255+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='otp'/><title type='text'>Blogger_srv.erl version 0.1 is here !</title><content type='html'>Here's the link to find the 'blogger_srv' you're waiting for: &lt;a href="http://googerl.googlecode.com/svn/trunk/src/blogger_srv.erl"&gt;blogger_srv.erl&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This is the first version of the code, I think I'll add other features later:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;auto reauth whenever the authtoken gets invalid&lt;/li&gt;&lt;li&gt;add some support for posting binary data, like images..&lt;/li&gt;&lt;li&gt;replace those antislashed double quotes by simple quotes ;)&lt;/li&gt;&lt;/ul&gt;For the time now, the code works, and all you need to do is starting the server:&lt;br /&gt;&lt;pre&gt;blogger_srv:init().&lt;/pre&gt;&lt;br /&gt;Call the 'auth' fun to obtain a valid 'AuthToken':&lt;br /&gt;&lt;pre&gt;blogger_srv:auth("youremail@gmail.com", "yourpass").&lt;/pre&gt;&lt;br /&gt;Finally call the 'new/4' or 'new/5' fun to post a message:&lt;br /&gt;&lt;pre&gt;blogger_srv:new(yourBlogId, Title, [Tags], &amp;lt;&amp;lt;Content&gt;&gt;).&lt;br /&gt;blogger_srv:new(yourBlogId, Title, [Tags], &amp;lt;&amp;lt;Content&gt;&gt;, AuthorName, AuthorEmail).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When the post is successfull, 'new/4' or 'new/5' will returns:&lt;br /&gt;&lt;pre&gt;{ok, NewPostId}&lt;/pre&gt;&lt;br /&gt;That's all for now !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-7048917043928101011?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/7048917043928101011/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=7048917043928101011' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/7048917043928101011'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/7048917043928101011'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/bloggersrverl-version-01-is-here.html' title='Blogger_srv.erl version 0.1 is here !'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-8263219475020092511</id><published>2007-08-07T14:30:00.001+02:00</published><updated>2007-08-07T22:11:45.810+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='gen_server'/><category scheme='http://www.blogger.com/atom/ns#' term='otp'/><title type='text'>Blogger gen_server, a running session</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_xJFAuTYeddk/RrhmLemC1_I/AAAAAAAAAAM/OeQLWYFCYlE/s1600-h/Screenshot.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://bp1.blogger.com/_xJFAuTYeddk/RrhmLemC1_I/AAAAAAAAAAM/OeQLWYFCYlE/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5095935325746943986" border="0" /&gt;&lt;/a&gt; Here's a screenshots of a sample session using 'blogger_srv'.&lt;br /&gt;You can see some of available funs 'auth', 'snap' and others...&lt;br /&gt;&lt;br /&gt;Here's the &lt;a href="http://code.google.com/p/googerl/"&gt;link&lt;/a&gt; of the google project.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-8263219475020092511?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/8263219475020092511/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=8263219475020092511' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8263219475020092511'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8263219475020092511'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/blogger-genserver-running-session.html' title='Blogger gen_server, a running session'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_xJFAuTYeddk/RrhmLemC1_I/AAAAAAAAAAM/OeQLWYFCYlE/s72-c/Screenshot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-9096533705174404306</id><published>2007-08-06T22:04:00.000+02:00</published><updated>2007-08-06T22:11:40.157+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='gen_server'/><category scheme='http://www.blogger.com/atom/ns#' term='otp'/><title type='text'>Blogger API gen_server !</title><content type='html'>I'm proud to announce the start of the Blogger API gen_server !&lt;br /&gt;&lt;br /&gt;I'll use the code I already wrote for testing the blogger API, and put some OTP requirement around it... For the moment there's three gen_server call:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;new: to create a new post&lt;/li&gt;&lt;li&gt;reset: to reset credentials, i.e. retrieve another AuthToken&lt;/li&gt;&lt;li&gt;auth: to retrieve the AuthToken (recomputing it if needed)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Extracted from the 'blogger_srv.erl' file:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;auth(Username, Password) -&gt;&lt;br /&gt; gen_server:call(?MODULE, {auth, Username, Password}).&lt;br /&gt;&lt;br /&gt;new(BlogId, Title, Tags, Content, {AuthorName, AuthorEmail} ) -&gt;&lt;br /&gt; gen_server:call(?MODULE, {post, BlogId, Title, Tags, Content, AuthorName, AuthorEmail}).&lt;br /&gt;&lt;br /&gt;reset() -&gt;&lt;br /&gt; gen_server:cast(?MODULE, reset).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For example, the 'post' fun:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;handle_call({post, BlogId, Title, Tags, Content, {AuthorName, AuthorEmail}}, _Node, State) -&gt;&lt;br /&gt; Requests = State#state.requests,&lt;br /&gt; Auth = State#state.auth,&lt;br /&gt; Data = entry_new(Title, {AuthorName, AuthorEmail}, Content, Tags),&lt;br /&gt; case request(Auth, BlogId, Data) of &lt;br /&gt;  {ok, PostId} -&gt;&lt;br /&gt;   {reply, {ok, PostId}, State#state{ requests = Requests + 1 } };&lt;br /&gt;&lt;br /&gt;  Msg -&gt;&lt;br /&gt;   {reply, {err, Msg}, State#state{ requests = Requests + 1 } }&lt;br /&gt; end;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-9096533705174404306?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/9096533705174404306/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=9096533705174404306' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/9096533705174404306'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/9096533705174404306'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/blogger-api-genserver.html' title='Blogger API gen_server !'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-349111540699225268</id><published>2007-08-06T21:54:00.000+02:00</published><updated>2007-08-06T22:01:40.332+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='http'/><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='post'/><category scheme='http://www.blogger.com/atom/ns#' term='parsing'/><title type='text'>The final request the post your Atom message.</title><content type='html'>The final request to post the message in the atom format:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;request(AuthToken, BlogId, Data) -&gt;&lt;br /&gt;        Body = iolist_to_binary(Data),&lt;br /&gt;        io:format("Sending: ~nContent-length: ~p~nBody:~n~s~n", [ size(Body), Body ]),&lt;br /&gt;        Authorization = "GoogleLogin auth=" ++ AuthToken,&lt;br /&gt;        Url = "http://www.blogger.com/feeds/" ++ BlogId ++ "/posts/default",&lt;br /&gt;        case http:request(post,                                         % Method ;)&lt;br /&gt;                        {&lt;br /&gt;                                Url,                                    % URL&lt;br /&gt;                                [ { "Authorization", Authorization } ], % Headers&lt;br /&gt;                                "application/atom+xml; charset=utf-8",  % Content-type&lt;br /&gt;                                Body                                    % Body&lt;br /&gt;                        },&lt;br /&gt;                [ {timeout, 3000}, {sync, false} ],             % HTTPOptions&lt;br /&gt;                [ {body_format, binary} ]) of                   % Options&lt;br /&gt;&lt;br /&gt;                {ok, Result} -&gt;&lt;br /&gt;                        case Result of&lt;br /&gt;                                {{_, 201, _}, Headers, _Response} -&gt;&lt;br /&gt;                                        PostId = get_postid(Headers),&lt;br /&gt;                                        {ok, PostId};&lt;br /&gt;&lt;br /&gt;                                {_,_,Response} -&gt;&lt;br /&gt;                                        Response&lt;br /&gt;                        end;&lt;br /&gt;&lt;br /&gt;                {error, Reason} -&gt;&lt;br /&gt;                        io:format("Error: ~p~n", [Reason])&lt;br /&gt;        end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The thing that's cool overthere is the 'get_postid/1' to retrieve the postId generated by the blogger api... This fun will be called only when the HTTP 'status' code will be "201", which means "created".&lt;br /&gt;&lt;br /&gt;I parse response headers and search for 'location' string, and once found I extract the last part of the url:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;get_postid([]) -&gt;&lt;br /&gt; "none";&lt;br /&gt;get_postid(Headers) -&gt;&lt;br /&gt; case lists:keysearch("location", 1, Headers) of&lt;br /&gt;  {value, {_, Value}} -&gt;&lt;br /&gt;   lists:last( string:tokens(Value, "/") );&lt;br /&gt;&lt;br /&gt;  _ -&gt;&lt;br /&gt;   "none"&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I've look at the Zend Framework, and found that it parse the entire response body to extract the same value... I think that my method is simpler and works better. That's also why erlang is for smarter people than php (gratuitous troll :p)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-349111540699225268?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/349111540699225268/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=349111540699225268' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/349111540699225268'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/349111540699225268'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/final-request-to-post-message-in-atom.html' title='The final request the post your Atom message.'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2094700941886317853</id><published>2007-08-06T21:44:00.000+02:00</published><updated>2007-08-06T22:00:33.891+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='atom'/><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='template'/><title type='text'>More things about the blogger API...</title><content type='html'>I've reworked many times the atom template I use to post things to blogger, and what I'll describe here is a design I found simple and efficient, so here's the code :&lt;br /&gt;&lt;br /&gt;This is the main function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;entry_new(Title, Author, { Content, ContentType }, Tags) when is_list(Tags) -&gt;&lt;br /&gt;        NTitle = post_title(Title),&lt;br /&gt;        NContent = post_content(Content, ContentType),&lt;br /&gt;        NTags = post_tags(Tags),&lt;br /&gt;        NAuthor = post_author(Author),&lt;br /&gt;        [&lt;br /&gt;                entry_header(),&lt;br /&gt;                NTitle,&lt;br /&gt;                NContent,&lt;br /&gt;                NAuthor,&lt;br /&gt;                NTags,&lt;br /&gt;                entry_footer()&lt;br /&gt;        ];&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The main idea is to use a list of binary, using this simple methods we don't mess with multiple copies or padding or strings management... This is quick and clean.&lt;br /&gt;&lt;br /&gt;Here's some little function to create simple tempates:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;entry_new(Title, Author, Content, Tags) -&gt;&lt;br /&gt;        entry_new(Title, Author, {Content, text}, Tags).&lt;br /&gt;&lt;br /&gt;entry_header() -&gt;&lt;br /&gt;        &amp;lt;&amp;lt;"&amp;lt;entry xmlns=\"http://www.w3.org/2005/Atom\"&gt;"&gt;&gt;.&lt;br /&gt;&lt;br /&gt;entry_footer() -&gt;&lt;br /&gt;        &amp;lt;&amp;lt;"&amp;lt;/entry&gt;"&gt;&gt;.&lt;br /&gt;        &lt;br /&gt;post_title(Title) -&gt;&lt;br /&gt;        [ &amp;lt;&amp;lt;"&amp;lt;title type=\"text\"&gt;"&gt;&gt;, list_to_binary(Title), &amp;lt;&amp;lt;"&amp;lt;/title&gt;"&gt;&gt; ].&lt;br /&gt;&lt;br /&gt;post_author({ AuthorName, AuthorEmail }) -&gt;&lt;br /&gt;        [ &lt;br /&gt;        &amp;lt;&amp;lt;"&amp;lt;author&gt;&amp;lt;name&gt;"&gt;&gt;, list_to_binary(AuthorName), &amp;lt;&amp;lt;"&amp;lt;/name&gt;&amp;lt;email&gt;"&gt;&gt;,&lt;br /&gt;        list_to_binary(AuthorEmail), &amp;lt;&amp;lt;"&amp;lt;/email&gt;&amp;lt;/author&gt;"&gt;&gt; ];&lt;br /&gt;post_author(AuthorEmail) -&gt;&lt;br /&gt;        post_author({ "", AuthorEmail }).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Here's some other function to carefully set the content-type with the post content:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;post_content(Content, ContentType) when is_list(Content) -&gt;&lt;br /&gt;        post_content(list_to_binary(Content), ContentType);&lt;br /&gt;post_content(Content, html) -&gt;&lt;br /&gt;        post_content(Content, "html");&lt;br /&gt;post_content(Content, xhtml) -&gt;&lt;br /&gt;        post_content(Content, "xhtml");&lt;br /&gt;post_content(Content, text) -&gt;&lt;br /&gt;        post_content(Content, "text");&lt;br /&gt;post_content(Content, ContentType) -&gt;&lt;br /&gt;        [ &amp;lt;&amp;lt;"&amp;lt;content type=\""&gt;&gt;, list_to_binary(ContentType), &amp;lt;&amp;lt;"\"&gt;"&gt;&gt;,&lt;br /&gt;        Content,&lt;br /&gt;        &amp;lt;&amp;lt;"&amp;lt;/content&gt;"&gt;&gt; ].&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And now the code the post 'tags':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;post_tags([]) -&gt;&lt;br /&gt;        &amp;lt;&amp;lt;&gt;&gt;;&lt;br /&gt;post_tags(List) -&gt;&lt;br /&gt;        lists:map(&lt;br /&gt;                fun(X) -&gt;&lt;br /&gt;                        [ &amp;lt;&amp;lt;"&amp;lt;category scheme='http://www.blogger.com/atom/ns#' term='"&gt;&gt;,&lt;br /&gt;                        list_to_binary(X) ,&lt;br /&gt;                        &amp;lt;&amp;lt;"'/&gt;"&gt;&gt; ]&lt;br /&gt;                end,&lt;br /&gt;                List).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The magic stands in the 'category' atom tag.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2094700941886317853?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2094700941886317853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2094700941886317853' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2094700941886317853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2094700941886317853'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/more-things-about-blogger-api.html' title='More things about the blogger API...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3584393048694693906</id><published>2007-08-02T14:58:00.000+02:00</published><updated>2007-08-02T15:04:06.385+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tags'/><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='posting'/><title type='text'>Blogger API, posting a message (with tags)</title><content type='html'>Here's some code from the current version:&lt;pre&gt;&lt;br /&gt;Data = entry_new("Test no1", {"tonio", "ako@gmail.com"}, &amp;lt;&amp;lt;"TestContent"&gt;&gt;, ["erlang", "test"]),&lt;br /&gt;request(Auth, iolist_to_binary(Data)).&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;The Title,&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The author, a tuple with authorName and authorEmail&lt;/li&gt;&lt;li&gt;A binary holding the content of the post&lt;/li&gt;&lt;li&gt;A list of tags&lt;/li&gt;&lt;/ul&gt;I think I'm on the right track...&lt;br /&gt;More news later...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3584393048694693906?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3584393048694693906/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3584393048694693906' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3584393048694693906'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3584393048694693906'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/blogger-api-posting-message-with-tags.html' title='Blogger API, posting a message (with tags)'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-8938337187472228629</id><published>2007-08-02T10:54:00.000+02:00</published><updated>2007-08-02T14:23:10.768+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='success'/><title type='text'>Erlang Blogger API  is working !!!</title><content type='html'>I've finally manage to get it working !&lt;br /&gt;The solution was in the AuthToken from my code, I didn't squeezed the final '\n' character !&lt;br /&gt;&lt;br /&gt;Later when the http request was generated, headers were split, making the GFE returning 400 Bad Request.&lt;br /&gt;&lt;br /&gt;This is the corrected 'extract_auth/1':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;extract_auth(&amp;lt;&amp;lt;"Auth=", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt;        Size = size(Rest) - 1,&lt;br /&gt;        &amp;lt;&amp;lt;Auth:Size/binary, _/binary&gt;&gt; = Rest,&lt;br /&gt;        {ok, Auth};&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I squeeze the final character !&lt;br /&gt;&lt;br /&gt;Here's a &lt;a href="http://groups.google.com/group/bloggerDev/browse_thread/thread/7b9193237f65c3dd?hl=en"&gt;link&lt;/a&gt; to the google groups discussion.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-8938337187472228629?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/8938337187472228629/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=8938337187472228629' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8938337187472228629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8938337187472228629'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/erlang-blogger-api-is-working.html' title='Erlang Blogger API  is working !!!'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-8898692829316366641</id><published>2007-08-01T22:01:00.000+02:00</published><updated>2007-08-01T22:10:47.772+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><title type='text'>Blogger API, xml sample</title><content type='html'>Here's the XML code is used as test:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;simple_post(AuthToken) -&gt;&lt;br /&gt; Data = &lt;br /&gt;&amp;lt;&amp;lt;"&amp;lt;?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?&gt;&lt;br /&gt;&amp;lt;entry xmlns=\"http://www.w3.org/2005/Atom\"&gt;&lt;br /&gt;&amp;lt;title type=\"text\"&gt;UberKwl&amp;lt;/title&gt;&lt;br /&gt;&amp;lt;content type=\"xhtml\"&gt;&lt;br /&gt;&amp;lt;div xmlns=\"http://www.w3.org/1999/xhtml\"&gt;&lt;br /&gt;       &amp;lt;p&gt;test post&amp;lt;/p&gt;&lt;br /&gt;&amp;lt;/div&gt;&lt;br /&gt;&amp;lt;/content&gt;&lt;br /&gt;&amp;lt;author&gt; &amp;lt;name&gt; Gautier &amp;lt;/name&gt; &amp;lt;email&gt; gauth@gmail.com &amp;lt;/email&gt;&lt;br /&gt;&amp;lt;/author&gt;&lt;br /&gt;&amp;lt;/entry&gt;"&gt;&gt;,&lt;br /&gt; request(AuthToken, Data).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Really, really simple, and this was extracted from a previous post somewhere in the google Blogger API groups...&lt;br /&gt;&lt;br /&gt;I've 'xmllint'ed it and of course it was correct...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-8898692829316366641?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/8898692829316366641/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=8898692829316366641' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8898692829316366641'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/8898692829316366641'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/08/blogger-api-xml-sample.html' title='Blogger API, xml sample'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-4698254061887326920</id><published>2007-07-31T21:49:00.000+02:00</published><updated>2007-07-31T22:08:47.508+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><title type='text'>Connecting Erlang to Blogger (Part 2) - Adding an entry</title><content type='html'>For this second part, we start where we left the last time.&lt;br /&gt;We were able to read the response of a succesful login, data was three lines of key value pairs.&lt;br /&gt;The last line holds the final 'AuthToken' we need to send to the blogger atom post service...&lt;br /&gt;&lt;br /&gt;So here's the code to extract the line that begins with the 'Auth' keyword and store the value after the '=' and before the end of line:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;extract_auth(&amp;lt;&amp;lt;&gt;&gt;) -&gt;&lt;br /&gt; {error, not_found};&lt;br /&gt;extract_auth(&amp;lt;&amp;lt;"Error=", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; Size = size(Rest) - 1,&lt;br /&gt; &amp;lt;&amp;lt;Msg:Size/binary, _/binary&gt;&gt; = Rest,&lt;br /&gt; {error, binary_to_list(Msg)};&lt;br /&gt;&lt;br /&gt;extract_auth(&amp;lt;&amp;lt;"Auth=", Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; {ok, Rest};&lt;br /&gt;extract_auth(&amp;lt;&amp;lt;_:1/binary, Rest/binary&gt;&gt;) -&gt;&lt;br /&gt; extract_auth(Rest).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Note that we are also able to read 'Error' lines, those lines are sent in case of an failed login attempt...&lt;br /&gt;I can describe what 'extract_auth/1' do like this:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;if binary in empty returns {error, not_found}&lt;/li&gt;&lt;li&gt;if binary begins with 'Error' catch it and returns its content with the tuple {error, Msg}&lt;/li&gt;&lt;li&gt;if binary begins with the 'Auth' keyword extract everything till the end of the binary&lt;/li&gt;&lt;li&gt;in any other case extract one character and parse the rest of the binary&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;To conclude, upon successful login this fun will return:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;{ok, "AAAAAQAARAFA..."}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is, of course our Blogger AuthToken...&lt;br /&gt;&lt;br /&gt;Now how can we use it ? This is Simple !&lt;br /&gt;Open a new file named blogger.erl and write something like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(blogger).&lt;br /&gt;-export([ new/3, post/3, test/2, template/3 ]).&lt;br /&gt;&lt;br /&gt;post(AuthToken, Title, Content) -&gt;&lt;br /&gt; Data = template(Title, Content),&lt;br /&gt; request(AuthToken, iolist_to_binary(Data)).&lt;br /&gt;&lt;br /&gt;template(Title, Content) when is_list(Content) -&gt;&lt;br /&gt; template(Title, Content, {"none", "none"}). &lt;br /&gt;&lt;br /&gt;template(Title, Content, Author) when is_list(Content) -&gt;&lt;br /&gt; template(Title, list_to_binary(Content), Author);&lt;br /&gt;&lt;br /&gt;template(Title, Content, Author) -&gt;&lt;br /&gt; {AuthorName, AuthorEmail} = Author,&lt;br /&gt; [ &amp;lt;&amp;lt;"&amp;lt;entry xmlns=\"http://www.w3.org/2005/Atom\"&gt;\n&amp;lt;title type=\"text\"&gt;\n"&gt;&gt;, &lt;br /&gt; list_to_binary(Title), &lt;br /&gt;% &amp;lt;&amp;lt;"&amp;lt;/title&gt;&amp;lt;content type='xhtml'&gt;&amp;lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;"&gt;&gt;, &lt;br /&gt; &amp;lt;&amp;lt;"&amp;lt;/title&gt;\n&amp;lt;content type=\"text\"&gt;"&gt;&gt;, &lt;br /&gt; Content, &lt;br /&gt; &amp;lt;&amp;lt;"&amp;lt;/content&gt;\n&amp;lt;author&gt;&amp;lt;name&gt;"&gt;&gt;,&lt;br /&gt; list_to_binary(AuthorName),&lt;br /&gt; &amp;lt;&amp;lt;"&amp;lt;/name&gt;&lt;email&gt;"&gt;&gt;,&lt;br /&gt; list_to_binary(AuthorEmail),&lt;br /&gt; &amp;lt;&amp;lt;"&amp;lt;&lt;/email&gt;&amp;lt;/author&gt;\n&amp;lt;/entry&gt;\n"&gt;&gt; ].&lt;br /&gt;&lt;br /&gt;request(AuthToken, Data) when is_binary(AuthToken) -&gt;&lt;br /&gt; request(binary_to_list(AuthToken), Data);&lt;br /&gt;&lt;br /&gt;request(AuthToken, Data) -&gt;&lt;br /&gt; io:format("Sending: ~nContent-length: ~p~nBody:~n~s~n", [ size(Data), Data ]),&lt;br /&gt; Authorization = "GoogleLogin auth=" ++ AuthToken,&lt;br /&gt; Url = "http://www.blogger.com/feeds/199963XXXX081936700/posts/default", % Put your BlogID, this one is invalid&lt;br /&gt; case http:request(post,  &lt;br /&gt;  { Url, &lt;br /&gt;   [ { "Authorization", Authorization } ], &lt;br /&gt;   "application/atom+xml; charset=utf-8", Data}, &lt;br /&gt;  [ {timeout, 3000}, {sync, false} ], &lt;br /&gt;  [ {body_format, binary} ]) of &lt;br /&gt;&lt;br /&gt;  {ok, Result} -&gt; &lt;br /&gt;   %io:format("Received: ~p~n", [Result]),&lt;br /&gt;   {_,_,Body} = Result,&lt;br /&gt;   Body;&lt;br /&gt; &lt;br /&gt;  {error, Reason} -&gt;&lt;br /&gt;   io:format("Error: ~p~n", [Reason])&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once you're done, you'll be pleased to found that this doesn't work :/.&lt;br /&gt;Yep, I wasn't able to post anything !&lt;br /&gt;May be I've missed something in the documentation, but all I get is an nice SAXexception...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-4698254061887326920?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/4698254061887326920/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=4698254061887326920' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4698254061887326920'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4698254061887326920'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/07/connecting-erlang-to-blogger-part-2.html' title='Connecting Erlang to Blogger (Part 2) - Adding an entry'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-4940173209973333725</id><published>2007-07-31T16:30:00.000+02:00</published><updated>2007-07-31T16:35:03.284+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='SaxParseException'/><category scheme='http://www.blogger.com/atom/ns#' term='error'/><title type='text'>Testing my Erlang Blogger API ...</title><content type='html'>I'm stuck on this error message !&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;&amp;lt;"org.xml.sax.SAXParseException: Content is not allowed in prolog."&gt;&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I'm simply using this code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;request(AuthToken, Data) -&gt;&lt;br /&gt;        io:format("Sending: ~nContent-length: ~p~nBody:~n~s~n", [ size(Data), Data ]),&lt;br /&gt;        Authorization = "GoogleLogin auth=" ++ AuthToken,&lt;br /&gt;        Url = "http://www.blogger.com/feeds/19996386XX081936700/posts/default",&lt;br /&gt;        case http:request(post,&lt;br /&gt;                { Url,&lt;br /&gt;                        [ { "Authorization", Authorization } ],&lt;br /&gt;                        "application/atom+xml; charset=utf-8", Data},&lt;br /&gt;                [ {timeout, 3000}, {sync, false} ],&lt;br /&gt;                [ {body_format, binary} ]) of&lt;br /&gt;&lt;br /&gt;                {ok, Result} -&gt;&lt;br /&gt;                        %io:format("Received: ~p~n", [Result]),&lt;br /&gt;                        {_,_,Body} = Result,&lt;br /&gt;                        Body;&lt;br /&gt;&lt;br /&gt;                {error, Reason} -&gt;&lt;br /&gt;                        io:format("Error: ~p~n", [Reason])&lt;br /&gt;        end.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the resulting Body is always the error:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;&amp;lt;"org.xml.sax.SAXParseException: Content is not allowed in prolog."&gt;&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Help Meeee !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-4940173209973333725?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/4940173209973333725/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=4940173209973333725' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4940173209973333725'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4940173209973333725'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/07/testing-my-erlang-blogger-api.html' title='Testing my Erlang Blogger API ...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3842166385324253347</id><published>2007-07-30T19:58:00.000+02:00</published><updated>2007-07-31T16:29:14.844+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='http'/><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='rest'/><category scheme='http://www.blogger.com/atom/ns#' term='post'/><category scheme='http://www.blogger.com/atom/ns#' term='api'/><category scheme='http://www.blogger.com/atom/ns#' term='gdata'/><category scheme='http://www.blogger.com/atom/ns#' term='form'/><title type='text'>Connecting Erlang to Blogger (Part 1) - Auth with ClientLogin</title><content type='html'>With the &lt;a href="http://code.google.com/apis/gdata/"&gt;Gdata&lt;/a&gt; API from google you can connect your application to some nice services... Calendar, Blogger etc.&lt;br /&gt;Since this is completly REST based you can of course use your 'http:request' to connect and exploit those services. Let's begin with the &lt;a href="http://code.google.com/apis/blogger/developers_guide_protocol.html#client_login"&gt;ClientLogin&lt;/a&gt; process.&lt;br /&gt;&lt;br /&gt;For this article we will focus on the Blogger API, the main purpose is of course create an Erlang client for Blogger :)&lt;br /&gt;&lt;br /&gt;Connecting to google is as simple as sending something like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;accountType=HOSTED_OR_GOOGLE&amp;Email=YOURGOOGLEACOUNT&amp;amp;Passwd=YOURPASSWORD&amp;source=SelfCo-TestApp-1&amp;amp;service=blogger&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now we can do it in Erlang too !. First we need to build the query string, second we need to send it to the ClientLogin service using 'http:request'.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;auth(Username, Password, Application) -&gt;&lt;br /&gt;    Sep = &amp;lt;&amp;lt;"&amp;"&gt;&gt;,&lt;br /&gt;    Post = [&lt;br /&gt;            &amp;lt;&amp;lt;"accountType=HOSTED_OR_GOOGLE&amp;"&gt;&gt;,&lt;br /&gt;            &amp;lt;&amp;lt;"Email="&gt;&gt;, list_to_binary(Username), Sep,&lt;br /&gt;            &amp;lt;&amp;lt;"Passwd="&gt;&gt;, list_to_binary(Password), Sep,&lt;br /&gt;            &amp;lt;&amp;lt;"source="&gt;&gt;, list_to_binary(Application), Sep,&lt;br /&gt;            &amp;lt;&amp;lt;"service=blogger"&gt;&gt; ],&lt;br /&gt;    request(erlang:iolist_to_binary(Post)).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The fun 'erlang:iolist_to_binary/1' transforms the list of binaries to a simple binary, this is not really necessary but this will ease yourself later for debugging...&lt;br /&gt;&lt;br /&gt;Now we can send this query string to the google ClientLogin process:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;request(Data) -&gt;&lt;br /&gt;      case http:request(post,&lt;br /&gt;          {"https://www.google.com/accounts/ClientLogin", [],&lt;br /&gt;           "application/x-www-form-urlencoded", Data},&lt;br /&gt;          [ {timeout, 3000} ], [{stream, "/tmp/google.test"}, {body_format, binary}]) of&lt;br /&gt;&lt;br /&gt;              {ok, saved_to_file}  -&gt;&lt;br /&gt;                      io:format("Saved to file~n");&lt;br /&gt;&lt;br /&gt;              {ok, Result} -&gt;&lt;br /&gt;                      io:format("Received: ~p~n", [Result]);&lt;br /&gt;&lt;br /&gt;              {error, Reason} -&gt;&lt;br /&gt;                      io:format("Error: ~p~n", [Reason])&lt;br /&gt;      end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;This is a POST query&lt;/li&gt;&lt;li&gt;The service is https://www.google.com/accounts/ClientLogin&lt;/li&gt;&lt;li&gt;The content-type is application/x-www-form-urlencoded&lt;/li&gt;&lt;li&gt;We sets the timeout to 3 seconds&lt;/li&gt;&lt;li&gt;We store the result (if successful to '/tmp/google.test')&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Let's try this code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;65&gt; google:auth("test@gmail.com", "secretcode").&lt;br /&gt;Received: {{"HTTP/1.1",403,"Forbidden"},&lt;br /&gt;           [{"cache-control","no-cache"},&lt;br /&gt;            {"date","Sun, 29 Jul 2007 20:44:20 GMT"},&lt;br /&gt;            {"pragma","no-cache"},&lt;br /&gt;            {"server","GFE/1.3"},&lt;br /&gt;            {"content-length","24"},&lt;br /&gt;            {"content-type","text/plain"}],&lt;br /&gt;           &lt;&lt;"Error=BadAuthentication\n"&gt;&gt;}&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The connection fails, so let's try with a valid user account:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;70&gt; google:auth("validaccount@gmail.com", "validpassword").&lt;br /&gt;Saved to file&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Success ! &lt;br /&gt;&lt;br /&gt;The content of '/tmp/google.test':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SID=DQAAAG8AAACuATb7YJxMdqQhp0LIf546SWLfDNfTlANffRc0B6OGbTat4Ebdj89s6hVEzfNZRL...&lt;br /&gt;LSID=DQAAAHEAAAAG1iqBgOrgzrY5cdgpBv9y42HxkvjNuUaYKImw6yH7xh0GtL5EG19C9GkGdPEb1...&lt;br /&gt;Auth=DQAAAHAAAAAG1iqBgOrgzrY5cdgpBv9y42HxkvjNuUaYKImw6yH7xh0GtL5EG19C9GkGdPEb1...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The final token we need is the 'Auth=' one, this string will be passed with every new query as an 'Authorization' header:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Authorization: GoogleLogin auth=DQAAAHAAAA...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next Time in Part 2, I'll show you how we'll use this AuthToken and how we will be able to post a message to our blog !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3842166385324253347?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3842166385324253347/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3842166385324253347' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3842166385324253347'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3842166385324253347'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/07/connecting-erlang-to-blogger-part-1.html' title='Connecting Erlang to Blogger (Part 1) - Auth with ClientLogin'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-1818959901864683818</id><published>2007-07-24T22:08:00.000+02:00</published><updated>2007-07-24T22:19:43.529+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ajp13'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='base'/><category scheme='http://www.blogger.com/atom/ns#' term='jboss'/><category scheme='http://www.blogger.com/atom/ns#' term='hexadecimal'/><title type='text'>Erlang and JBOSS, talking AJP13 ! (PART I)</title><content type='html'>&lt;pre&gt;&lt;br /&gt;-module(ajp13).&lt;br /&gt;-export([get/3, cping/2, request/1, hexdump/1]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Every ajp packets starts with 0x1234. In Erlang when you need to express this thing you just need to use the notation 'Base#number'.&lt;br /&gt;So for our example, here's the 'ajp_header' fun :&lt;br /&gt;&lt;pre&gt;   &lt;br /&gt;ajp_header() -&gt;&lt;br /&gt;        &amp;lt;&amp;lt;16#12, 16#34&gt;&gt;.&lt;br /&gt;&lt;/pre&gt;  &lt;br /&gt;        &lt;br /&gt;We use the binary notation to write 2 bytes expressed on base 16 (hexadecimal). To be crystal clear hex notation can be written with '16#'... :  &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Eshell V5.5.1  (abort with ^G)&lt;br /&gt;1&gt; 16#deadbeef. &lt;br /&gt;3735928559              &lt;br /&gt;&lt;/pre&gt;                  &lt;br /&gt;I'm sure you get the point !&lt;br /&gt;                &lt;br /&gt;Let's comes back to our AJP problem... Now that we can write hexadecimal number we can reread the ajp13 protocol description,&lt;br /&gt;and succesfully start to build a simple packet:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;get(Host, Port, Url) -&gt; &lt;br /&gt;        H = ajp_header(), &lt;br /&gt;        Request = request(Url),&lt;br /&gt;        Length = size(Request), &lt;br /&gt;        Data = &amp;lt;&amp;lt;H/binary, Length:16, Request/binary&gt;&gt;,&lt;br /&gt;        &lt;br /&gt;        case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of&lt;br /&gt;                {ok, Socket} -&gt;&lt;br /&gt;                        send(Socket, Data),&lt;br /&gt;                        loop(Socket);&lt;br /&gt;&lt;br /&gt;                {error, Msg} -&gt;&lt;br /&gt;                        io:format("error: ~p~n", [Msg])&lt;br /&gt;        end.            &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's look at a simple command in the ajp13 protocol, the 'ping', here's its implementation:&lt;br /&gt;&lt;pre&gt;                                   &lt;br /&gt;cping() -&gt;&lt;br /&gt;        &amp;lt;&amp;lt;                &lt;br /&gt;        10:8                            &lt;br /&gt;        &gt;&gt;.&lt;br /&gt;&lt;br /&gt;cping(Host, Port) -&gt;&lt;br /&gt;        H = ajp_header(),&lt;br /&gt;        Request = cping(),&lt;br /&gt;        Length = size(Request),&lt;br /&gt;        Data = &amp;lt;&amp;lt;H/binary, Length:16, Request/binary&gt;&gt;,&lt;br /&gt;&lt;br /&gt;        case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of&lt;br /&gt;                {ok, Socket} -&gt;&lt;br /&gt;                        send(Socket, Data),&lt;br /&gt;                        loop(Socket);&lt;br /&gt;&lt;br /&gt;                {error, Msg} -&gt;&lt;br /&gt;                        io:format("error: ~p~n", [Msg])&lt;br /&gt;        end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;What's important to see is also that ajp13 is derived from the xdr protocol, where every type is always written with its length... In ajp13 this length  is always encoded as two bytes (16bits so max size is 16#ffff ;)&lt;br /&gt;&lt;br /&gt;The 'Data' variable is what you should look at :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;        Data = &amp;lt;&amp;lt;H/binary, Length:16, Request/binary&gt;&gt;,&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;H is the ajp header&lt;/li&gt;&lt;li&gt;Length is the length of Request written on 2 bytes (2 * 8)&lt;/li&gt;&lt;li&gt;Request is the request&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Now that we've sent the packet, we need to catch the response, so here's the 'loop' fun:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(Socket) -&gt;&lt;br /&gt;        receive&lt;br /&gt;                {tcp, Socket, Data} -&gt;&lt;br /&gt;                        % io:format("~p~n", [Data]),&lt;br /&gt;&lt;br /&gt;                        case ajp_response(Data, Socket) of&lt;br /&gt;                                {ok, continue} -&gt;&lt;br /&gt;                                        loop(Socket);&lt;br /&gt;&lt;br /&gt;                                {ok, body, Bin} -&gt;&lt;br /&gt;                                        io:format("Body: read ~p bytes~n", [size(Bin)]),&lt;br /&gt;                                        loop(Socket);&lt;br /&gt;&lt;br /&gt;                                {ok, closed} -&gt;&lt;br /&gt;                                        gen_tcp:close(Socket)&lt;br /&gt;                        end;&lt;br /&gt;&lt;br /&gt;                {tcp_error, Socket, Error} -&gt;&lt;br /&gt;                        io:format("Error: ~p~n", [Error]),&lt;br /&gt;                        loop(Socket);&lt;br /&gt;&lt;br /&gt;                {tcp_closed, Socket} -&gt;&lt;br /&gt;                        io:format("Closed~n")&lt;br /&gt;&lt;br /&gt;        after 8000 -&gt;&lt;br /&gt;                io:format("Timeout~n"),&lt;br /&gt;                gen_tcp:close(Socket)&lt;br /&gt;&lt;br /&gt;        end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Whenever our erlang process will receive a message matching the '{tcp, Socket, Data}' tuple we will parse the 'Data' with the 'ajp_response' fun:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ajp_response(&amp;lt;&amp;lt;65,66,0,2,5,1&gt;&gt;, _Socket) -&gt;&lt;br /&gt;        {ok, closed};&lt;br /&gt;ajp_response(&amp;lt;&amp;lt;65,66,Rest/binary&gt;&gt;, Socket) -&gt;&lt;br /&gt;        ajp_data_length(Rest, Socket);&lt;br /&gt;ajp_response(Bin, Socket) -&gt;&lt;br /&gt;        {ok, body, Bin}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Yeah ! Polymorphism ! Or matching power ?! Whatever, this completely rox the programming planet !&lt;br /&gt;We are simply matching binary data... Binary data that's sent back to us from the jboss server (in our case).&lt;br /&gt;&lt;br /&gt;Ajp13 protocol describes the termination of the request by a packet containing 'AB' followed by the response length '2' bytes which are '5' and '1'.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ajp_response(&amp;lt;&amp;lt;65,66,0,2,5,1&gt;&gt;, _Socket) -&gt;&lt;br /&gt;        {ok, closed};&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Remember Length is encoded on two bytes: '0,2'...&lt;br /&gt;&lt;br /&gt;Now comes the AJP13_FORWARD_REQUEST !!!&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;request(Request) -&gt;&lt;br /&gt;        {Protocol, L0} = ajp_string("HTTP/1.1"),&lt;br /&gt;        {Request_uri, L1} = ajp_string(Request),&lt;br /&gt;        {Remote_addr, L2} = ajp_string("127.0.0.1"),&lt;br /&gt;        {Remote_host, L3} = ajp_string("ajbchecker"),&lt;br /&gt;        {Server_name, L4} = ajp_string("www.server-example.com"),&lt;br /&gt;&lt;br /&gt;        &amp;lt;&amp;lt;&lt;br /&gt;        2:8,                            %byte JK_AJP13_FORWARD_REQUEST&lt;br /&gt;        2:8,                            %byte GET&lt;br /&gt;        L0:16, Protocol/binary,         %string&lt;br /&gt;        L1:16, Request_uri/binary,      %string&lt;br /&gt;        L2:16, Remote_addr/binary,      %string&lt;br /&gt;        L3:16, Remote_host/binary,      %string&lt;br /&gt;        L4:16, Server_name/binary,      %string&lt;br /&gt;        80:16,                          %integer&lt;br /&gt;        0:8,                            %boolean&lt;br /&gt;        1:16,                           %integer&lt;br /&gt;        16#A0, 16#0B,                   %Header: Host&lt;br /&gt;        L4:16, Server_name/binary,      %Servername&lt;br /&gt;        16#ff                           %terminator&lt;br /&gt;        &gt;&gt;.&lt;br /&gt;&lt;br /&gt;ajp_string(String) -&gt;&lt;br /&gt;        S = list_to_binary(String),&lt;br /&gt;        Bin = &amp;lt;&amp;lt;S/binary, 0&gt;&gt;,&lt;br /&gt;        {Bin, size(Bin) - 1}.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The 'ajp_string/1' is used to calculate the final size of the binary data, and is simply used with the ajp13 string encoding format...&lt;br /&gt;&lt;br /&gt;BTW, it's really time consuming to explain code ! When I started this article I was thinking that I'll finish it rather quickly, and I realise now that's not the case, there's so many things to say... &lt;br /&gt;This is why I stop here for the First Part, the next part will come tomorrow...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-1818959901864683818?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/1818959901864683818/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=1818959901864683818' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1818959901864683818'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/1818959901864683818'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/07/erlang-and-jboss-talking-ajp13-part-i.html' title='Erlang and JBOSS, talking AJP13 ! (PART I)'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-6717288158187277142</id><published>2007-07-19T21:52:00.000+02:00</published><updated>2007-07-19T22:20:26.743+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='parallelisation'/><category scheme='http://www.blogger.com/atom/ns#' term='external'/><category scheme='http://www.blogger.com/atom/ns#' term='message'/><title type='text'>Parallelizing simple external commands ... Part II</title><content type='html'>Our loop/3 fun looks like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(_Max, 0, []) -&gt;&lt;br /&gt;     unregister(computing_master),&lt;br /&gt;     exit(normal);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Whenever our list of jobs is empty, we deregister the 'computing_master' process and quit normally.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(Max, Current, []) -&gt;&lt;br /&gt;     receive&lt;br /&gt;             stop -&gt;&lt;br /&gt;                     unregister(computing_master),&lt;br /&gt;                     exit(normal);&lt;br /&gt;&lt;br /&gt;             {exited, _Result} -&gt;&lt;br /&gt;                     io:format("Still ~p childs~n", [Current]),&lt;br /&gt;                     loop(Max, Current - 1, []);&lt;br /&gt;&lt;br /&gt;             E -&gt;&lt;br /&gt;                     io:format("Unhandled message: ~p~n", [E])&lt;br /&gt;&lt;br /&gt;     after 60000 -&gt;&lt;br /&gt;             io:format("~p: Waiting for the last process ~p/~p~n", [erlang:now(), Max, Current]),&lt;br /&gt;             loop(Max, Current, [])&lt;br /&gt;     end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In this case, we have are computing the last external process since our job list is empty.&lt;br /&gt;And finally this version of loop/3 is the main one:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;loop(Max, Current, List)  -&gt;&lt;br /&gt;     receive&lt;br /&gt;             stop -&gt;&lt;br /&gt;                     unregister(computing_master),&lt;br /&gt;                     exit(normal);&lt;br /&gt;&lt;br /&gt;             {update, NewMax} -&gt;&lt;br /&gt;                     upto(NewMax, Max, List);&lt;br /&gt;&lt;br /&gt;             {exited, _Result} -&gt;&lt;br /&gt;                     io:format("Still ~p childs~n", [Max]),&lt;br /&gt;                     upto(Max, Max - 1, List);&lt;br /&gt;&lt;br /&gt;             E -&gt;&lt;br /&gt;                     io:format("Unhandled message: ~p~n", [E])&lt;br /&gt;&lt;br /&gt;     after 60000 -&gt;&lt;br /&gt;             io:format("~p: Running ~p processes~n", [erlang:now(), Max]),&lt;br /&gt;             upto(Max, Current, List)&lt;br /&gt;     end.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here we have a non empty list of job and a number of job to start.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Every 60 seconds we write how many processes are running.&lt;/li&gt;&lt;li&gt;The message {update, NewMax} let's you alter the number max of concurrent tasks&lt;/li&gt;&lt;li&gt;The message {exited, _Result} is received whenever a child process dies, so we restart another job...&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Bonus Code, a simple function to test the code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;sleep(Ident) -&gt;&lt;br /&gt;        io:format("Waiting ~p~n", [Ident]),&lt;br /&gt;        Delay = [ "5", "3", "15", "8" ],&lt;br /&gt;        Time = lists:nth(random:uniform(4), Delay),&lt;br /&gt;        Cmd = [ "sleep ", Time ], &lt;br /&gt;        io:format("Starting: ~p~n", [Cmd]),&lt;br /&gt;        Status = os:cmd(Cmd),&lt;br /&gt;        computing_master ! {exited, Status}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This code just calls the 'sleep' command with various arguments picked randomly... Once a process  stops the 'os:cmd/1' fun exits and 'computing_master' will receive the {exited, Status} message (explained above)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-6717288158187277142?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/6717288158187277142/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=6717288158187277142' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6717288158187277142'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/6717288158187277142'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/07/parallelizing-simple-external-commands_19.html' title='Parallelizing simple external commands ... Part II'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3147991593551544018</id><published>2007-07-17T23:33:00.000+02:00</published><updated>2007-07-19T22:14:05.805+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='synchronisation'/><category scheme='http://www.blogger.com/atom/ns#' term='parallelisation'/><category scheme='http://www.blogger.com/atom/ns#' term='process'/><category scheme='http://www.blogger.com/atom/ns#' term='execution'/><category scheme='http://www.blogger.com/atom/ns#' term='external'/><title type='text'>Parallelizing simple external commands ... Part I</title><content type='html'>Once upon a time I need to parse enormous files to find simple patterns... My prefered tools were so far the shell based one, i.e. 'grep'.&lt;br /&gt;&lt;br /&gt;But now I have a Magical Ability, Erlang Magic... So I decide to split this enormous file, using the 'split' comand, 'split -l 10000' for example.&lt;br /&gt;&lt;br /&gt;Now that I have a lot of smaller file, I can parallelize their parsing, and this is were erlang comes...&lt;br /&gt;&lt;br /&gt;First, let's design a bit:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I need a central process that will control all my processes&lt;/li&gt;&lt;li&gt;Processes and master must be able to communicate&lt;/li&gt;&lt;/ul&gt;That's all. Hopefully the latter is directly provided by erlang, this the ! operator.&lt;br /&gt;The master process will be a little more tricky, but this is 'easyerl' remember, so here we go:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;doit(Step) -&gt;&lt;br /&gt;    Master = spawn(?MODULE, test, [Step]),&lt;br /&gt;    register(computing_master, Master).&lt;br /&gt;&lt;br /&gt;test(Step) -&gt;&lt;br /&gt;    file:set_cwd("/home/rolphin/Work"),&lt;br /&gt;    List = filelib:wildcard("seg-a*"),&lt;br /&gt;    upto(Step, 0, List).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We create a process running the test function, whose job is starting the upto/3 fun...&lt;br /&gt;What's interesting here is the 'filelib' function that provides me the list of file contained in the directory '/home/rolphin/Work'.&lt;br /&gt;&lt;br /&gt;Now we go describe the 'upto/3' fun :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;upto(Max, Current, []) -&gt;&lt;br /&gt;    loop(Max, Current, []);&lt;br /&gt;&lt;br /&gt;upto(Max, Max, List) -&gt;&lt;br /&gt;    loop(Max, Max, List);&lt;br /&gt;&lt;br /&gt;upto(Max, Current, [New|List]) -&gt;&lt;br /&gt;    io:format("upto: ~p/~p~n", [Max, Current]),&lt;br /&gt;    spawn(?MODULE, grep, ["user.list", New, ["result-", New]]),&lt;br /&gt;    upto(Max, Current + 1, List).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;More details:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;upto with an empty list will just call the loop/3 fun&lt;/li&gt;&lt;li&gt;upto with the Max number of processe allowed equals the current number of process, will call loop/3&lt;/li&gt;&lt;li&gt;upto with less active process than the max, with a non empty list, will spawn a child process&lt;/li&gt;&lt;/ul&gt;The child process is a 'grep' command, and here it is:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;grep(File, Source, Result) -&gt;&lt;br /&gt;       % Command line is: "grep -f motif_file sourcefile &gt; result"&lt;br /&gt;       Cmd = [ "grep -f ", File , $ , Source, $&gt;, Result ],&lt;br /&gt;       io:format("Starting: ~p~n", [Cmd]),&lt;br /&gt;       Status = os:cmd(Cmd),&lt;br /&gt;       computing_master ! {exited, Status}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Okay that's it for today ! It's a little late now ! And I need some sleep to succesfully pass the required skill tests for my new job !&lt;br /&gt;&lt;br /&gt;More of this tomorrow...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3147991593551544018?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3147991593551544018/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3147991593551544018' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3147991593551544018'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3147991593551544018'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/07/parallelizing-simple-external-commands.html' title='Parallelizing simple external commands ... Part I'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-3871460225909545509</id><published>2007-07-03T11:05:00.000+02:00</published><updated>2007-07-03T11:10:19.942+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='port'/><category scheme='http://www.blogger.com/atom/ns#' term='execution'/><category scheme='http://www.blogger.com/atom/ns#' term='external'/><title type='text'>Simple command execution</title><content type='html'>Sometimes you need to run external commands, and just need the return value or exit code...&lt;br /&gt;One simple way to do this is the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(tport).&lt;br /&gt;-export([ execute/2 ]).&lt;br /&gt;&lt;br /&gt;execute(Host, Cmd) -&gt;&lt;br /&gt; Port = open_port({spawn, Cmd}, [ {cd, Host}, exit_status, binary ] ),&lt;br /&gt; wait(Port).&lt;br /&gt;&lt;br /&gt;wait(Port) -&gt;&lt;br /&gt; receive &lt;br /&gt;  {Port, {data, BinData}} -&gt;&lt;br /&gt;   io:format("dump:~n~p~n", [BinData]),&lt;br /&gt;   wait(Port);&lt;br /&gt;  {Port, {exit_status, Status}} -&gt;&lt;br /&gt;   io:format("exit_code: ~p~n", [Status]);&lt;br /&gt;  %% {Port, eof} -&gt;&lt;br /&gt;  %%  port_close(Port);&lt;br /&gt;  {Port, exit} -&gt;&lt;br /&gt;   io:format("Received : ~p~n", [Port])&lt;br /&gt; end.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once a port opened your process will receive various messages and one we're interested in is the 'exit_status' one:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  {Port, {exit_status, Status}} -&gt;&lt;br /&gt;   io:format("exit_code: ~p~n", [Status]);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The variable 'Status' will hold the exit code.&lt;br /&gt;&lt;br /&gt;Simple isn't it ?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-3871460225909545509?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/3871460225909545509/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=3871460225909545509' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3871460225909545509'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/3871460225909545509'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/07/simple-command-execution.html' title='Simple command execution'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-848879376120089465</id><published>2007-06-26T20:56:00.000+02:00</published><updated>2007-06-26T22:21:14.449+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='template'/><category scheme='http://www.blogger.com/atom/ns#' term='open_port'/><title type='text'>Using ports for fast templating...</title><content type='html'>I find myself many times searching for fast templating system, and I always stop my search by developing one myself. ( the one is done using flex generated code, which means that it is very fast... )&lt;br /&gt;&lt;br /&gt;Here, I need some fast (rather simple) template system that gives me some data to send later to an SMTP server (the body of the mail).&lt;br /&gt;&lt;br /&gt;First the code:&lt;br /&gt;&lt;pre&gt;get(Template, Args) -&gt;&lt;br /&gt;     NewArgs = list_to_shell(Args),&lt;br /&gt;     Cmd = lists:flatten([ ?engine, $ , "conf/template/", Template, $ , NewArgs ]),&lt;br /&gt;     Port = open_port({spawn, Cmd}, [ {cd, code:lib_dir(mmailer)}, stream, binary]),&lt;br /&gt;     process_flag(trap_exit, true),&lt;br /&gt;     loop(Port).&lt;br /&gt;&lt;/pre&gt;You'll find some unknown notation: '?engine' and $.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;?engine is a macro, defined like this -define(engine, "templater").&lt;/li&gt;&lt;li&gt;$ is the character notation, the character that follows the dollar sign is the character I want. Actually I need the space character...&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Now the function list_to_shell:&lt;br /&gt;&lt;pre&gt;list_to_shell(Args) -&gt;&lt;br /&gt;      L = lists:map(fun(X) -&gt; [X, $ ] end, Args),&lt;br /&gt;      lists:flatten(L).&lt;br /&gt;&lt;/pre&gt;This function add a space after after element of the array passed as parameter. I construct the shell command this way.&lt;br /&gt;&lt;br /&gt;The call to open_port is the main part of the code:&lt;br /&gt;&lt;pre&gt;Port = open_port({spawn, Cmd}, [ {cd, code:lib_dir(mmailer)}, stream, binary]),&lt;br /&gt;&lt;/pre&gt;The following describe what the above line does:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;spawn the command 'Cmd' &lt;/li&gt;&lt;li&gt;in the directory code:lib_dir(mmailer), &lt;/li&gt;&lt;li&gt;this command will write data as a stream &lt;/li&gt;&lt;li&gt;and I want erlang to give me a binary stream. &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;(code:lib_dir(mmailer) gives me the directory where the mmailer module is located)&lt;br /&gt;&lt;br /&gt;More about my engine... My engine takes as firts arguments the template file to parse and every other parameter is saved as $1, or $2, or $3 etc.&lt;br /&gt;&lt;br /&gt;Example of a template 'test.template'&lt;br /&gt;&lt;pre&gt;Hello $1 $2 !&lt;br /&gt;We have a good news for you, join us at url/$3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So calling:&lt;br /&gt;&lt;pre&gt;templater test.template Mister John connect?John&lt;br /&gt;&lt;/pre&gt;Will compute the following:&lt;br /&gt;&lt;pre&gt;Hello Mister John !&lt;br /&gt;We have a good news for you, join us at url/connect?John&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Conclusion, you can use whatever template system efficiently with the open_port function, since you can dynamically build the command line.&lt;br /&gt;&lt;br /&gt;NB: I've not secure the line generated by list_to_shell, any '; rm -rf *' in the command line will do nasty things with your stuff :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-848879376120089465?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/848879376120089465/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=848879376120089465' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/848879376120089465'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/848879376120089465'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/using-ports-for-fast-templating.html' title='Using ports for fast templating...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-4017277845546362032</id><published>2007-06-26T12:10:00.000+02:00</published><updated>2007-06-26T12:13:44.392+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='variable'/><category scheme='http://www.blogger.com/atom/ns#' term='atoms'/><category scheme='http://www.blogger.com/atom/ns#' term='functions'/><title type='text'>Atoms...</title><content type='html'>Atoms, Variable, what they are and what you can do with them ?&lt;br /&gt;&lt;br /&gt;Atoms are what you'll find strange at the first look, you may ask yourself&lt;br /&gt;continously, but where the hell this thing is defined !!&lt;br /&gt;&lt;br /&gt;Relax and just read the following words, atoms are just there to document your code...&lt;br /&gt;So they can appear anywhere, have no special meaning or just may be funny !&lt;br /&gt;You'll find them most of the time tagging tuples, example:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;test() -&gt;&lt;br /&gt;        % code that fails&lt;br /&gt;        {error, "Can't find file"}.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This way you can try to match 'error', and bind a Variable to the reason:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;case test() of&lt;br /&gt;        {ok, Result} -&gt;&lt;br /&gt;                Result;&lt;br /&gt;        {error, Reason} -&gt;&lt;br /&gt;                io:format("Error: because ~p~n", [Reason])&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Simple isn't it ?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-4017277845546362032?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/4017277845546362032/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=4017277845546362032' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4017277845546362032'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/4017277845546362032'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/atoms.html' title='Atoms...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-7237198907524898695</id><published>2007-06-12T00:04:00.001+02:00</published><updated>2007-06-26T12:10:50.430+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='process'/><category scheme='http://www.blogger.com/atom/ns#' term='functions'/><title type='text'>Erlang and calling functions</title><content type='html'>In Erlang, when you want to call a function you can use various notations:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;modulename:functionname(arguments).&lt;br /&gt;functionname(arguments).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Calling a function in another process:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;NewPid = spawn(modulename, functionname, [arguments]).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now a simple test module:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(test).&lt;br /&gt;-export([test/1]).&lt;br /&gt;&lt;br /&gt;test(String) -&gt;&lt;br /&gt;  { erlang:now(), String }.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This module has one function named 'test', its arity is one (one parameter), and this function&lt;br /&gt;returns a tuple that contains the datetime and the 'string' passed as the parameter.&lt;br /&gt;&lt;br /&gt;Calling 'erlang:now()':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(master@karoten)52&gt; erlang:now().&lt;br /&gt;{1181,599629,877590}&lt;br /&gt;(master@karoten)53&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is some sort of UNIX timestamp (number of seconds since 1970).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now test our module:&lt;br /&gt;&lt;br /&gt;Compiling it, ie compiling 'test.erl' located in the current directory:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(master@karoten)52&gt; c(test).&lt;br /&gt;ok&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Calling the funcname 'test':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(master@karoten)53&gt; test:test("test").&lt;br /&gt;{{1181,599702,993670},"test"}&lt;br /&gt;(master@karoten)54&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's we have used the notation 'modulename:functionname(arguments)'. &lt;br /&gt;But within the module itself we could use the notation 'functionname(arguments)'...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(test).&lt;br /&gt;-export([test/1, test/0]).&lt;br /&gt;&lt;br /&gt;test(String) -&gt;&lt;br /&gt;  { erlang:now(), String }.&lt;br /&gt;&lt;br /&gt;test() -&gt;&lt;br /&gt;  test("test").&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's come what's really important to understand in Erlang, a function is defined by its name AND its arity. &lt;br /&gt;So 'test/0' is NOT 'test/1'.&lt;br /&gt;&lt;br /&gt;In our module, what the function 'test/0' do is just calling 'test/1' with a string that contains 'test'.&lt;br /&gt;&lt;br /&gt;Erlang is a functionnal language, so the parameter of the function 'test/1' can really be what you want, from simple string to integer to complex list of tuple...&lt;br /&gt;&lt;br /&gt;Just try this, and you'll understand :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;test:test( [ complex, list, of, atoms, "and a string" ] ).&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-7237198907524898695?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/7237198907524898695/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=7237198907524898695' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/7237198907524898695'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/7237198907524898695'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/erlang-and-calling-functions.html' title='Erlang and calling functions'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-469581644580591181</id><published>2007-06-10T15:38:00.002+02:00</published><updated>2008-06-26T12:03:39.386+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bit syntax'/><category scheme='http://www.blogger.com/atom/ns#' term='binary'/><category scheme='http://www.blogger.com/atom/ns#' term='stream'/><title type='text'>Shrink or Strip a binary octet-stream easily</title><content type='html'>I've found a simple way to suppress ending characters of a Binary without parsing it or change it into a list !&lt;br /&gt;&lt;br /&gt;I wanted to remove those &lt;&lt;"\r\n"&gt;&gt; characters from lines read from a file, and know that my line are made of 29 characters (reading date strings). &lt;br /&gt;&lt;br /&gt;So with this function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;shrinkbin(Data, Size) -&gt;&lt;br /&gt;  &lt;&amp;lt;Data:Size/binary-unit:8&amp;gt;&gt;.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I can do:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;node&gt; test:shrinkbin(&lt;&lt;"Sun Jun 10 15:20:53 CEST 2007\r\n"&gt;&gt;, 29).&lt;br /&gt;&lt;&lt;"Sun Jun 10 15:20:53 CEST 2007"&gt;&gt;.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's it !&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-469581644580591181?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/469581644580591181/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=469581644580591181' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/469581644580591181'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/469581644580591181'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/shrink-binary-octet-stream-easily.html' title='Shrink or Strip a binary octet-stream easily'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-7585203413587787110</id><published>2007-06-10T00:56:00.000+02:00</published><updated>2007-06-26T12:16:14.939+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bit syntax'/><category scheme='http://www.blogger.com/atom/ns#' term='SMTP'/><category scheme='http://www.blogger.com/atom/ns#' term='protocol'/><category scheme='http://www.blogger.com/atom/ns#' term='matching'/><title type='text'>Matching Protocol status code using Binary notation .</title><content type='html'>Once you're succesfully connected to some remote host, you may need to read what this peer sends you...&lt;br /&gt;&lt;br /&gt;Sometimes the protocol is using simple integer number to describe what's going on. Let's have a look at the SMTP protocol:&lt;br /&gt;2XX are ok codes,&lt;br /&gt;5XX are error codes.&lt;br /&gt;&lt;br /&gt;Testing Binary matching:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(bintest).&lt;br /&gt;-export([test/0, check/1]).&lt;br /&gt;&lt;br /&gt;test() -&gt;&lt;br /&gt;   Bin = &lt;&lt;"200 OK\r\n"&amp;gt;&amp;gt;,&lt;br /&gt;   check(Bin).&lt;br /&gt;&lt;br /&gt;check(&lt;&lt;"200", Rest/binary&amp;gt;&amp;gt;) -&gt;&lt;br /&gt;   {200, Rest};&lt;br /&gt;check(&lt;&lt;"300", Rest/binary&amp;gt;&amp;gt;) -&gt;&lt;br /&gt;   {300, Rest};&lt;br /&gt;check(_Bin) -&gt;&lt;br /&gt;   {800, unknown}.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-7585203413587787110?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/7585203413587787110/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=7585203413587787110' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/7585203413587787110'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/7585203413587787110'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/matching-protocol-status-code-using.html' title='Matching Protocol status code using Binary notation .'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-2376034497756444817</id><published>2007-06-09T17:15:00.000+02:00</published><updated>2007-06-10T23:22:20.887+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DNS'/><category scheme='http://www.blogger.com/atom/ns#' term='sorting'/><category scheme='http://www.blogger.com/atom/ns#' term='MX record'/><category scheme='http://www.blogger.com/atom/ns#' term='lists'/><title type='text'>Sorting Mx servers using their preference number...</title><content type='html'>Once you've retrieved your mx lists, you want to be kind enough to gently contact mx server in their prefered order...&lt;br /&gt;&lt;br /&gt;In erlang, sorting a list is done with sort function like 'keysort'.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SortedList = lists:keysort(1, List).&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;lists:keysort uses as first parameter, the nth element of the tuple to use in the sort, and as second parameter the tuple list you want to sort...&lt;br /&gt;&lt;br /&gt;Once you've sorted your tuple list, you may want to remove the key you use to sort, lists:unzip remove this...&lt;br /&gt;&lt;br /&gt;Finally, retrieving a mx list, sorting it, and directly using it can be done like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;mxlist(Domain) -&gt;&lt;br /&gt; List = get_mx(Domain),&lt;br /&gt; {_, Hosts} = lists:unzip( lists:keysort(1, List) ),&lt;br /&gt; Hosts.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-2376034497756444817?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/2376034497756444817/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=2376034497756444817' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2376034497756444817'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/2376034497756444817'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/sorting-mx-servers-using-their.html' title='Sorting Mx servers using their preference number...'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-359903065350269776</id><published>2007-06-07T08:49:00.000+02:00</published><updated>2007-06-10T23:23:51.139+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DNS'/><category scheme='http://www.blogger.com/atom/ns#' term='inet_db'/><category scheme='http://www.blogger.com/atom/ns#' term='inet_res'/><category scheme='http://www.blogger.com/atom/ns#' term='MX record'/><title type='text'>Configuring your DNS server for inet_res.</title><content type='html'>When using inet_res, you must configure a DNS server to query, this could be done like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-define(MASTER_DNS, {212,XX,XX,252}).&lt;br /&gt;&lt;br /&gt;init() -&gt;&lt;br /&gt; inet_db:add_ns(?MASTER_DNS).&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once you've called your ?MODULE:init/1 you're able to use your ?MODULE:get_mx/1 function.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;node&gt; mail:init().&lt;br /&gt;ok&lt;br /&gt;&lt;br /&gt;node&gt; mail:get_mx("yahoo.com").                                                       &lt;br /&gt;[{1,"e.mx.mail.yahoo.com"},&lt;br /&gt; {1,"a.mx.mail.yahoo.com"},&lt;br /&gt; {1,"b.mx.mail.yahoo.com"},&lt;br /&gt; {1,"c.mx.mail.yahoo.com"},&lt;br /&gt; {1,"d.mx.mail.yahoo.com"},&lt;br /&gt; {1,"f.mx.mail.yahoo.com"},&lt;br /&gt; {1,"g.mx.mail.yahoo.com"}]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The  result is a list of tuples, the first element is the server weight, the second the server name :p&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-359903065350269776?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/359903065350269776/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=359903065350269776' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/359903065350269776'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/359903065350269776'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/configuring-your-dns-server-for-inetres.html' title='Configuring your DNS server for inet_res.'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-855944390206940143.post-548119020319953773</id><published>2007-06-06T23:32:00.000+02:00</published><updated>2007-06-10T23:24:36.333+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DNS'/><category scheme='http://www.blogger.com/atom/ns#' term='inet_res'/><category scheme='http://www.blogger.com/atom/ns#' term='MX record'/><title type='text'>Retrieve MX DNS record using erlang inet_res, it's easy !</title><content type='html'>Ever needed to retrieve some MX servers from any big domains out there ?&lt;br /&gt;&lt;br /&gt;I'm pretty sure that you've asked yourself this question, right ?&lt;br /&gt;&lt;br /&gt;It's EasyErl here, so let's go to the code directly:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;get_mx(Domain) -&gt;&lt;br /&gt;       {ok, {hostent, Domain, _, _, _Len, List}} = inet_res:getbyname(Domain, mx),&lt;br /&gt;       List.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Woh ! This wasn't too much difficult !&lt;br /&gt;&lt;br /&gt;Okay, inet_res:getbyname isn't really well documented that's right, but Erlang comes with its source code, what's a better Erlang documentation than Erlang himself ?&lt;br /&gt;&lt;br /&gt;The next time, I'll show you how you can easily sort the List by using unzip to obtain a correct list of MX servers... (where servers weight are correctly used)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/855944390206940143-548119020319953773?l=easyerl.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://easyerl.blogspot.com/feeds/548119020319953773/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=855944390206940143&amp;postID=548119020319953773' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/548119020319953773'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/855944390206940143/posts/default/548119020319953773'/><link rel='alternate' type='text/html' href='http://easyerl.blogspot.com/2007/06/retrieve-mx-dns-record-using-erlang.html' title='Retrieve MX DNS record using erlang inet_res, it&apos;s easy !'/><author><name>Antoine</name><uri>http://www.blogger.com/profile/15993500368732301898</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>
