Alessio Treglia: The logical contradictions of the Universe
Ouroboros
Ouroboros
It all started last June when my son had his Janoi (Yagnopavita) ceremony -- the ritual by which a Brahmana boy becomes "twice-born" and eligible to study the Vedas. As well as a profound religious experience, it is also an important social occasion with a reception for as many friends and family as can attend. (I think our final guest total was ~250.) This meant new outfits for everyone which might be exciting for some people but not me. I still don't know why I couldn't just keep wearing the khes and pitambar from the puja but no, apparently that's a faux pas. So I relented and agreed to wear my "darbari" suit from my wedding. And it didn't fit. I knew I had gained some weight in the intermediate 17 years but the thing was sitcom levels of too small. I ended up having to purchase a new one, a snazzy (and shiny!) maroon number with gold stripes (or were they black stripes?) Problem having been solved, much was eaten, more weight was gained and then I forgot about the whole thing.
Tip 1: Actually Do Something.
I have over the years tried to improve my physical condition but it has never gotten very far. For instance I have a treadmill/coatrack and a couple of years ago I began using it in earnest. I got to the point where I actually ran a 10K race without dying. But I did not train systematically and I ended up in some pain which caused me to stop working out for a while and then I never got around to restarting. Diets have also failed because I don't have a clear idea of what and how much I am eating. All I know is that women go into the kitchen and when they come out they have food. By what eldritch process this occurs is a mystery, I just eat whats given to me thankful that the magic happens. Once I was moved to try and help but quickly fell afoul of the lack of well-defined algorithms in Gujarati home cooking.
"How much saffron should I add?"
"this much."
"How much is this much in SI units?"
"You're annoying me. Get out."Fast forward to March of this year. For my birthday, my wife got me a Fitbit fitness tracker. This is what I had needed all this time. It measure heart rate, distance travelled, time slept and several other pieces of info you can use to really plan a fitness regimen rationally. For example, I was chagrined to learn that sometimes when I'm at the computer, I am so immobile that the fitbit thought I was asleep. So I started planning to taken more frequent breaks. (A recent firmware upgrade has added the ability to nudge to walk atleast 250 paces each daytime hour which is handy for this.) Also by checking my heart rate I discovered that I went on the treadmill I ran too fast thereby stressing my body for little gain and ending up going too slow to get much aerobic effect. Now I can pace myself appropriately for maximum cardiac efficiency without ending up injuring myself and giving up. I also get a little more activity each day by simple changes such as taking the stairs instead of the lift and instead of getting off at the 14th street PATH I go all the way to 34th street and walk down. Tip 2: You must have data in order to see what you did right or wrong and to plan what you need to do moving forward. One caveat about these fitness trackers. They are not anywhere as accurate as a proper checkup from a doctor who specializes in such things. If you want to do any kind of pro or amateur athletics you probably should not rely on them but for the average shlub who just wants to avoid appearing on the news being winched off his sofa by the fire brigade they are good enough. Another practice I began was keeping a food diary. It can be a real eye-opener to see how much you are actually eating. It is probably much more than you thought. I am fortunate that my diet is pretty good to begin with. Vegetarian, (not vegan, Hindus eat dairy products,) mostly home-cooked with fresh ingredients, not fried or processed, and I don't drink alcohol. However there were a few optimizations I could make. I drink a lot of soda; atleast two cans a day. I really ought to stop altogether but in lieu of that I have atleast switched from Coke to Coke Zero thereby saving a lot of empty calories. I now eat 4 rotlis with my dinner instead of six. We as a family eat more green vegetables instead of potatos, skim milk instead of whole fat, canola oil instead of corn oil, and less rice and don't slather ghee on everything quite so much. One entirely new practice I've adopted that may seem faddish but works for me is intermittent fasting. The idea is to steadily train your body to need less food by eating all your days allowed amount pf calories during a 6-8 hour window and not eating at all during the remaining time. It's hard to get used to for many people but I fast atleast 2-3 times a month for religious reasons anyway so I adapted pretty quickly. The fitbit tells me how many calories I am expending and how many I can eat to maintain a healthy level of weight loss but other than that I don't bother with "food groups" or specific diets such as paleo, or low-carb etc. As long as what you eat is reasonably balanced and you are burning more calories than you are adding, it should be enough for weight loss. Indeed from the end of March to now, I've lost 3 stones (20Kg) even with the occasional "cheat" day. Tip 3: All published diets are bullshit without scientifically proven efficacy. Don't bother with them. Experiment instead and see what works for you and your metabolism. As long as you are getting all the proper nutrients (you shouldn't need a supplement unless you have an actual medical condition.) and you have a net calorie deficit, it's all good. If you eat food you enjoy, you are more likely to stick to your diet. The proper amount of sleep is one area of a healthy lifestyle I am still doing poorly in and the reasons are not all raven-related. I have always had problems with insomnia and was once actually diagnosed with sleep apnea. Losing weight has helped a lot but the fitbit is still reporting that I toss and turn a lot during the night. And that's when I'm in bed in the first place. I stay up much too late which can also lead to subsidiary bad behaviours such as midnight snacking. It's something I need to work on. Tip 4: Stop blogging at all hours of the night, It's not doing you any good. So that's what I'm doing. Moving forward, I need to deal with the sleep thing and I would also like to start some program of strength-training, I'm doing ok in terms of aerobic exercise but from what I've read, you also have to build up muscles to keep weight loss permanent. The difficulty is that it would involve joining a gym and then actually going to that gym so I've put it off for now. The immediate threat is Diwali (and Thanksgiving and Christmas...) My wife bought 4 lbs of sweets today and I can feel their presence in the fridge calling to me.
docs/metadata) that's
mostly JSON plus some documentation fragments and generates README,
the web page for the package (in thread, the macro language I use for all
my web pages), and (the other thing I've wanted to do and didn't want to
tackle without this tool) README.md, a Markdown version of
README that will look nice on GitHub.
The templates that come with the package are all rather specific to me,
particularly the thread template which would be unusable by anyone else.
I have no idea if anyone else will want to use this package (and right now
the metadata format is entirely undocumented). But since it's a shame to
not release things as free software, and since I suspect I may need to
upload it to Debian since, technically, this tool is required to "build"
the README file distributed with my packages, here it is. I've
also uploaded it to CPAN (it's my first experiment with the App::*
namespace for things that aren't really meant to be used as a standalone
Perl module).
You can get the latest version from the DocKnot distribution page (which is indeed generated with DocKnot).
Also now generated with DocKnot are the rra-c-util and C TAP Harness
distribution pages. Let me know if you see anything weird; there are
doubtless still a few bugs.
As web search engines and IRC seems to be of no help, maybe someone
here has a helpful idea. I have some service written in python that
comes with a .service file for systemd. I now want to build&install a
working service file from the software's setup.py. I can override
the build/build_py commands of setuptools, however that way I still
lack knowledge wrt. the bindir/prefix where my service script will be
installed.
Solution
Turns out, if you override the install command (not the
install_data!), you will have self.root and self.install_scripts
(and lots of other self.install_*). As a result, you can read the
template and write the desired output file after calling super's
run method. The fix was inspired by GateOne (which, however
doesn't get the --root parameter right, you need to strip
self.root from the beginning of the path to actually make that work
as intended).
As suggested on IRC, the snippet (and my software) no use pkg-config
to get at the systemd path as well. This is a nice improvement
orthogonal to the original problem. The implementation here follows
bley.
def systemd_unit_path():
try:
command = ["pkg-config", "--variable=systemdsystemunitdir", "systemd"]
path = subprocess.check_output(command, stderr=subprocess.STDOUT)
return path.decode().replace('\n', '')
except (subprocess.CalledProcessError, OSError):
return "/lib/systemd/system"
class my_install(install):
_servicefiles = [
'foo/bar.service',
]
def run(self):
install.run(self)
if not self.dry_run:
bindir = self.install_scripts
if bindir.startswith(self.root):
bindir = bindir[len(self.root):]
systemddir = "%s%s" % (self.root, systemd_unit_path())
for servicefile in self._servicefiles:
service = os.path.split(servicefile)[1]
self.announce("Creating %s" % os.path.join(systemddir, service),
level=2)
with open(servicefile) as servicefd:
servicedata = servicefd.read()
with open(os.path.join(systemddir, service), "w") as servicefd:
servicefd.write(servicedata.replace("%BINDIR%", bindir))
Comments, suggestions and improvements, of course, welcome!
Nageru 1.4.0 is out (and on its way through
the Debian upload process right now), so now you can do live video mixing with multichannel
audio to your heart's content. I've already blogged about most of the
interesting new features, so instead, I'm trying to answer a question:
What took so long?
To be clear, I'm not saying 1.4.0 took more time than I really anticipated
(on the contrary, I pretty much understood the scope from the beginning,
and there was a reason why I didn't go for building this stuff into 1.0.0);
but if you just look at the changelog from the outside, it's not immediately
obvious why multichannel audio support should take the better part of three
months of develoment. What I'm going to say is of course going to be obvious
to most software developers, but not everyone is one, and perhaps my
experiences will be illuminating.
Let's first look at some obvious things that isn't the case: First of all,
development is not primarily limited by typing speed. There are about 9,000
lines of new code in 1.4.0 (depending a bit on how you count), and if it was
just about typing them in, I would be done in a day or two. On a good
keyboard, I can type plain text at more than 800 characters per minute but
you hardly ever write code for even a single minute at that speed. Just as
when writing a novel, most time is spent thinking, not typing.
I also didn't spend a lot of time backtracking; most code I wrote actually
ended up in the finished product as opposed to being thrown away. (I'm not
as lucky in all of my projects.) It's pretty
common to do so if you're in an exploratory phase, but in this case, I had a
pretty good idea of what I wanted to do right from the start, and that plan
seemed to work. This wasn't a difficult project per se; it just needed
to be done (which, in a sense, just increases the mystery).
However, even if this isn't at the forefront of science in any way (most code
in the world is pretty pedestrian, after all), there's still a lot of
decisions to make, on several levels of abstraction. And a lot of those
decisions depend on information gathering beforehand. Let's take a look at
an example from late in the development cycle, namely support for using MIDI
controllers instead of the mouse to control the various widgets.
I've kept a pretty meticulous TODO list; it's just a text file on my laptop,
but it serves the purpose of a ghetto bugtracker. For 1.4.0, it contains 83
work items (a single-digit number is not ticked off, mostly because I decided
not to do those things), which corresponds roughly 1:2 to the number of
commits. So let's have a look at what the ~20 MIDI controller items went into.
First of all, to allow MIDI controllers to influence the UI, we need a way
of getting to it. Since Nageru is single-platform on Linux, ALSA is the
obvious choice (if not, I'd probably have to look for a library to put
in-between), but seemingly, ALSA has two interfaces (raw MIDI and sequencer).
Which one do you want? It sounds like raw MIDI is what we want, but actually,
it's the sequencer interface (it does more of the MIDI parsing for you,
and generally is friendlier).
The first question is where to start picking events from. I went the simplest
path and just said I wanted all events anything else would necessitate a UI,
a command-line flag, figuring out if we wanted to distinguish between
different devices with the same name (and not all devices potentially even
have names), and so on. But how do you enumerate devices? (Relatively simple,
thankfully.) What do you do if the user inserts a new one while Nageru is
running? (Turns out there's a special device you can subscribe to that will
tell you about new devices.) What if you get an error on subscription?
(Just print a warning and ignore it; it's legitimate not to have access to
all devices on the system. By the way, for PCM devices, all of these answers
are different.)
So now we have a sequencer device, how do we get events from it? Can we do it in the main loop? Turns out
it probably doesn't integrate too well with Qt, but it's easy enough to put
it in a thread. The class dealing with the MIDI handling now needs locking;
what mutex granularity do we want? (Experience will tell you that you nearly
always just want one mutex. Two mutexes give you all sorts of headaches with
ordering them, and nearly never gives any gain.) ALSA expects us to poll()
a given set of descriptors for data, but on shutdown, how do you break out
of that poll to tell the thread to go away? (The simplest way on Linux is
using an eventfd.)
There's a quirk where if you get two or more MIDI messages right after each
other and only read one, poll() won't trigger to alert you there are more
left. Did you know that? (I didn't. I also can't find it documented. Perhaps
it's a bug?) It took me some looking into sample code to find it. Oh, and
ALSA uses POSIX error codes to signal errors (like nothing more is
available ), but it doesn't use errno.
OK, so you have events (like controller 3 was set to value 47 ); what do you do
about them? The meaning of the controller numbers is different from
device to device, and there's no open format for describing them. So I had to
make a format describing the mapping; I used protobuf (I have lots of
experience with it) to make a simple text-based format, but it's obviously
a nightmare to set up 50+ controllers by hand in a text file, so I had to
make an UI for this. My initial thought was making a grid of spinners
(similar to how the input mapping dialog already worked), but then I realized
that there isn't an easy way to make headlines in Qt's grid. (You can
substitute a label widget for a single cell, but not for an entire row.
Who knew?) So after some searching, I found out that it would be better
to have a tree view (Qt Creator does this), and then you can treat that
more-or-less as a table for the rows that should be editable.
Of course, guessing controller numbers is impossible even in an editor,
so I wanted it to respond to MIDI events. This means the editor needs
to take over the role as MIDI receiver from the main UI. How you do
that in a thread-safe way? (Reuse the existing mutex; you don't generally
want to use atomics for complicated things.) Thinking about it, shouldn't the
MIDI mapper just support multiple receivers at a time? (Doubtful; you don't
want your random controller fiddling during setup to actually influence
the audio on a running stream. And would you use the old or the new mapping?)
And do you really need to set up every single controller for each bus,
given that the mapping is pretty much guaranteed to be similar for them?
Making a guess bus button doesn't seem too difficult, where if you
have one correctly set up controller on the bus, it can guess from
a neighboring bus (assuming a static offset). But what if there's
conflicting information? OK; then you should disable the button.
So now the enable/disable status of that button depends on which cell
in your grid has the focus; how do you get at those events? (Install an event
filter, or subclass the spinner.) And so on, and so on, and so on.
You could argue that most of these questions go away with experience;
if you're an expert in a given API, you can answer most of these questions
in a minute or two even if you haven't heard the exact question before.
But you can't expect even experienced developers to be an expert in all
possible libraries; if you know everything there is to know about Qt,
ALSA, x264, ffmpeg, OpenGL, VA-API, libusb, microhttpd and Lua
(in addition to C++11, of course), I'm sure you'd be a great fit for
Nageru, but I'd wager that pretty few developers fit that bill.
I've written C++ for almost 20 years now (almost ten of them professionally),
and that experience certainly helps boosting productivity, but I can't
say I expect a 10x reduction in my own development time at any point.
You could also argue, of course, that spending so much time on the editor
is wasted, since most users will only ever see it once. But here's the
point; it's not actually a lot of time. The only reason why it seems
like so much is that I bothered to write two paragraphs about it;
it's not a particular pain point, it just adds to the total. Also,
the first impression matters a lot if the user can't get the editor
to work, they also can't get the MIDI controller to work, and is likely
to just go do something else.
A common misconception is that just switching languages or using libraries
will help you a lot. (Witness the never-ending stream of software that
advertises written in Foo or uses Bar as if it were a feature.)
For the former, note that nothing I've said so far is specific to my choice
of language (C++), and I've certainly avoided a bunch of battles by making
that specific choice over, say, Python. For the latter, note that most of these problems are actually related
to library use libraries are great, and they solve a bunch of problems
I'm really glad I didn't have to worry about (how should each button look?),
but they still give their own interaction problems. And even when you're a
master of your chosen programming environment, things still take time,
because you have all those decisions to make on top of your libraries.
Of course, there are cases where libraries really solve your entire problem
and your code gets reduced to 100 trivial lines, but that's really only when
you're solving a problem that's been solved a million times before. Congrats
on making that blog in Rails; I'm sure you're advancing the world. (To make
things worse, usually this breaks down when you want to stray ever so
slightly from what was intended by the library or framework author. What
seems like a perfect match can suddenly become a development trap where you
spend more of your time trying to become an expert in working around the
given library than actually doing any development.)
The entire thing reminds me of the famous essay
No Silver Bullet by Fred
Brooks, but perhaps even more so, this quote from
John Carmack's .plan has
struck with me (incidentally about mobile game development in 2006,
but the basic story still rings true):
To some degree this is already the case on high end BREW phones today. I have a pretty clear idea what a maxed out software renderer would look like for that class of phones, and it wouldn't be the PlayStation-esq 3D graphics that seems to be the standard direction. When I was doing the graphics engine upgrades for BREW, I started along those lines, but after putting in a couple days at it I realized that I just couldn't afford to spend the time to finish the work. "A clear vision" doesn't mean I can necessarily implement it in a very small integral number of days.In a sense, programming is all about what your program should do in the first place. The how question is just the what , moved down the chain of abstractions until it ends up where a computer can understand it, and at that point, the three words multichannel audio support have become those 9,000 lines that describe in perfect detail what's going on.
My plan for this week s lecture of the CIS 194 Haskell course at the University of Pennsylvania is to dwell a bit on the concept of Functor, Applicative and Monad, and to highlight the value of the Applicative abstraction.
I quite like the example that I came up with, so I want to share it here. In the interest of long-term archival and stand-alone presentation, I include all the material in this post.1
import Data.Char
import Data.Maybe
import Data.List
import System.Environment
import System.IO
import System.Exit
newtype Parser a = P (String -> Maybe (a, String))
runParser :: Parser t -> String -> Maybe (t, String)
runParser (P p) = p
parse :: Parser a -> String -> Maybe a
parse p input = case runParser p input of
Just (result, "") -> Just result
_ -> Nothing -- handles both no result and leftover input
noParserP :: Parser a
noParserP = P (\_ -> Nothing)
pureParserP :: a -> Parser a
pureParserP x = P (\input -> Just (x,input))
instance Functor Parser where
fmap f p = P $ \input -> do
(x, rest) <- runParser p input
return (f x, rest)
instance Applicative Parser where
pure = pureParserP
p1 <*> p2 = P $ \input -> do
(f, rest1) <- runParser p1 input
(x, rest2) <- runParser p2 rest1
return (f x, rest2)
instance Monad Parser where
return = pure
p1 >>= k = P $ \input -> do
(x, rest1) <- runParser p1 input
runParser (k x) rest1
anyCharP :: Parser Char
anyCharP = P $ \input -> case input of
(c:rest) -> Just (c, rest)
[] -> Nothing
charP :: Char -> Parser ()
charP c = do
c' <- anyCharP
if c == c' then return ()
else noParserP
anyCharButP :: Char -> Parser Char
anyCharButP c = do
c' <- anyCharP
if c /= c' then return c'
else noParserP
letterOrDigitP :: Parser Char
letterOrDigitP = do
c <- anyCharP
if isAlphaNum c then return c else noParserP
orElseP :: Parser a -> Parser a -> Parser a
orElseP p1 p2 = P $ \input -> case runParser p1 input of
Just r -> Just r
Nothing -> runParser p2 input
manyP :: Parser a -> Parser [a]
manyP p = (pure (:) <*> p <*> manyP p) orElseP pure []
many1P :: Parser a -> Parser [a]
many1P p = pure (:) <*> p <*> manyP p
sepByP :: Parser a -> Parser () -> Parser [a]
sepByP p1 p2 = (pure (:) <*> p1 <*> (manyP (p2 *> p1))) orElseP pure []
A parser using this library for, for example, CSV files could take this form:
parseCSVP :: Parser [[String]]
parseCSVP = manyP parseLine
where
parseLine = parseCell sepByP charP ',' <* charP '\n'
parseCell = do
charP '"'
content <- manyP (anyCharButP '"')
charP '"'
return content
cell = '"', not-quote , '"';
line = (cell, ',', cell ''), newline;
csv = line ;
It is straightforward to create a Haskell data type to represent an EBNF syntax description. Here is a simple EBNF library (data type and pretty-printer) for your convenience:
data RHS
= Terminal String
NonTerminal String
Choice RHS RHS
Sequence RHS RHS
Optional RHS
Repetition RHS
deriving (Show, Eq)
ppRHS :: RHS -> String
ppRHS = go 0
where
go _ (Terminal s) = surround "'" "'" $ concatMap quote s
go _ (NonTerminal s) = s
go a (Choice x1 x2) = p a 1 $ go 1 x1 ++ " " ++ go 1 x2
go a (Sequence x1 x2) = p a 2 $ go 2 x1 ++ ", " ++ go 2 x2
go _ (Optional x) = surround "[" "]" $ go 0 x
go _ (Repetition x) = surround " " " " $ go 0 x
surround c1 c2 x = c1 ++ x ++ c2
p a n a > n = surround "(" ")"
otherwise = id
quote '\'' = "\\'"
quote '\\' = "\\\\"
quote c = [c]
type Production = (String, RHS)
type BNF = [Production]
ppBNF :: BNF -> String
ppBNF = unlines . map (\(i,rhs) -> i ++ " = " ++ ppRHS rhs ++ ";")
RHS type directly, but we can do something more nifty: We create a data type that keeps track, via a phantom type parameter, of what Haskell type the given EBNF syntax is the specification:
newtype Grammar a = G RHS
ppGrammar :: Grammar a -> String
ppGrammar (G rhs) = ppRHS rhs
So a value of type Grammar t is a description of the textual representation of the Haskell type t.
Here is one simple example:
anyCharG :: Grammar Char
anyCharG = G (NonTerminal "char")
Here is another one. This one does not describe any interesting Haskell type, but is useful when spelling out the special characters in the syntax described by the grammar:
charG :: Char -> Grammar ()
charG c = G (Terminal [c])
A combinator that creates new grammar from two existing grammars:
orElseG :: Grammar a -> Grammar a -> Grammar a
orElseG (G rhs1) (G rhs2) = G (Choice rhs1 rhs2)
We want the convenience of our well-known type classes in order to combine these values some more:
instance Functor Grammar where
fmap _ (G rhs) = G rhs
instance Applicative Grammar where
pure x = G (Terminal "")
(G rhs1) <*> (G rhs2) = G (Sequence rhs1 rhs2)
Note how the Functor instance does not actually use the function. How should it? There are no values inside a Grammar!
We cannot define a Monad instance for Grammar: We would start with (G rhs1) >>= k = , but there is simply no way of getting a value of type a that we can feed to k. So we will do without a Monad instance. This is interesting, and we will come back to that later.
Like with the parser, we can now begin to build on the primitive example to build more complicated combinators:
manyG :: Grammar a -> Grammar [a]
manyG p = (pure (:) <*> p <*> manyG p) orElseG pure []
many1G :: Grammar a -> Grammar [a]
many1G p = pure (:) <*> p <*> manyG p
sepByG :: Grammar a -> Grammar () -> Grammar [a]
sepByG p1 p2 = ((:) <$> p1 <*> (manyG (p2 *> p1))) orElseG pure []
Let us run a small example:
dottedWordsG :: Grammar [String]
dottedWordsG = many1G (manyG anyCharG <* charG '.')
*Main> putStrLn $ ppGrammar dottedWordsG
'', ('', char, ('', char, ('', char, ('', char, ('', char, ('',
Oh my, that is not good. Looks like the recursion in manyG does not work well, so we need to avoid that. But anyways we want to be explicit in the EBNF grammars about where something can be repeated, so let us just make many a primitive:
manyG :: Grammar a -> Grammar [a]
manyG (G rhs) = G (Repetition rhs)
With this definition, we already get a simple grammar for dottedWordsG:
*Main> putStrLn $ ppGrammar dottedWordsG
'', char , '.', char , '.'
This already looks like a proper EBNF grammar. One thing that is not nice about it is that there is an empty string ('') in a sequence ( , ). We do not want that.
Why is it there in the first place? Because our Applicative instance is not lawful! Remember that pure id <*> g == g should hold. One way to achieve that is to improve the Applicative instance to optimize this case away:
instance Applicative Grammar where
pure x = G (Terminal "")
G (Terminal "") <*> G rhs2 = G rhs2
G rhs1 <*> G (Terminal "") = G rhs1
(G rhs1) <*> (G rhs2) = G (Sequence rhs1 rhs2)
Now we get what we want:
*Main> putStrLn $ ppGrammar dottedWordsG
char , '.', char , '.'
Remember our parser for CSV files above? Let me repeat it here, this time using only Applicative combinators, i.e. avoiding (>>=), (>>), return and do-notation:
parseCSVP :: Parser [[String]]
parseCSVP = manyP parseLine
where
parseLine = parseCell sepByP charG ',' <* charP '\n'
parseCell = charP '"' *> manyP (anyCharButP '"') <* charP '"'
And now we try to rewrite the code to produce Grammar instead of Parser. This is straightforward with the exception of anyCharButP. The parser code for that inherently monadic, and we just do not have a monad instance. So we work around the issue by making that a primitive grammar, i.e. introducing a non-terminal in the EBNF without a production rule pretty much like we did for anyCharG:
primitiveG :: String -> Grammar a
primitiveG s = G (NonTerminal s)
parseCSVG :: Grammar [[String]]
parseCSVG = manyG parseLine
where
parseLine = parseCell sepByG charG ',' <* charG '\n'
parseCell = charG '"' *> manyG (primitiveG "not-quote") <* charG '"'
Of course the names parse are not quite right any more, but let us just leave that for now.
Here is the result:
*Main> putStrLn $ ppGrammar parseCSVG
('"', not-quote , '"', ',', '"', not-quote , '"' ''), '
'
The line break is weird. We do not really want newlines in the grammar. So let us make that primitive as well, and replace charG '\n' with newlineG:
newlineG :: Grammar ()
newlineG = primitiveG "newline"
Now we get
*Main> putStrLn $ ppGrammar parseCSVG
('"', not-quote , '"', ',', '"', not-quote , '"' ''), newline
which is nice and correct, but still not quite the easily readable EBNF that we saw further up.
newtype Grammar a = G (BNF, RHS)
runGrammer :: String -> Grammar a -> BNF
runGrammer main (G (prods, rhs)) = prods ++ [(main, rhs)]
ppGrammar :: String -> Grammar a -> String
ppGrammar main g = ppBNF $ runGrammer main g
Now we have to adjust all our primitive combinators (but not the derived ones!):
charG :: Char -> Grammar ()
charG c = G ([], Terminal [c])
anyCharG :: Grammar Char
anyCharG = G ([], NonTerminal "char")
manyG :: Grammar a -> Grammar [a]
manyG (G (prods, rhs)) = G (prods, Repetition rhs)
mergeProds :: [Production] -> [Production] -> [Production]
mergeProds prods1 prods2 = nub $ prods1 ++ prods2
orElseG :: Grammar a -> Grammar a -> Grammar a
orElseG (G (prods1, rhs1)) (G (prods2, rhs2))
= G (mergeProds prods1 prods2, Choice rhs1 rhs2)
instance Functor Grammar where
fmap _ (G bnf) = G bnf
instance Applicative Grammar where
pure x = G ([], Terminal "")
G (prods1, Terminal "") <*> G (prods2, rhs2)
= G (mergeProds prods1 prods2, rhs2)
G (prods1, rhs1) <*> G (prods2, Terminal "")
= G (mergeProds prods1 prods2, rhs1)
G (prods1, rhs1) <*> G (prods2, rhs2)
= G (mergeProds prods1 prods2, Sequence rhs1 rhs2)
primitiveG :: String -> Grammar a
primitiveG s = G (NonTerminal s)
The use of nub when combining productions removes duplicates that might be used in different parts of the grammar. Not efficient, but good enough for now.
Did we gain anything? Not yet:
*Main> putStr $ ppGrammar "csv" (parseCSVG)
csv = ('"', not-quote , '"', ',', '"', not-quote , '"' ''), newline ;
But we can now introduce a function that lets us tell the system where to give names to a piece of grammar:
nonTerminal :: String -> Grammar a -> Grammar a
nonTerminal name (G (prods, rhs))
= G (prods ++ [(name, rhs)], NonTerminal name)
Ample use of this in parseCSVG yields the desired result:
parseCSVG :: Grammar [[String]]
parseCSVG = manyG parseLine
where
parseLine = nonTerminal "line" $
parseCell sepByG charG ',' <* newline
parseCell = nonTerminal "cell" $
charG '"' *> manyG (primitiveG "not-quote") <* charG '"
*Main> putStr $ ppGrammar "csv" (parseCSVG)
cell = '"', not-quote , '"';
line = (cell, ',', cell ''), newline;
csv = line ;
This is great!
parseCSVG and parseCSVP are! Would it not be great if we could implement that functionality only once, and get both a parser and a grammar description out of it? This way, the two would never be out of sync!
And surely this must be possible. The tool to reach for is of course to define a type class that abstracts over the parts where Parser and Grammer differ. So we have to identify all functions that are primitive in one of the two worlds, and turn them into type class methods. This includes char and orElse. It includes many, too: Although manyP is not primitive, manyG is. It also includes nonTerminal, which does not exist in the world of parsers (yet), but we need it for the grammars.
The primitiveG function is tricky. We use it in grammars when the code that we might use while parsing is not expressible as a grammar. So the solution is to let it take two arguments: A String, when used as a descriptive non-terminal in a grammar, and a Parser a, used in the parsing code.
Finally, the type classes that we except, Applicative (and thus Functor), are added as constraints on our type class:
class Applicative f => Descr f where
char :: Char -> f ()
many :: f a -> f [a]
orElse :: f a -> f a -> f a
primitive :: String -> Parser a -> f a
nonTerminal :: String -> f a -> f a
The instances are easily written:
instance Descr Parser where
char = charP
many = manyP
orElse = orElseP
primitive _ p = p
nonTerminal _ p = p
instance Descr Grammar where
char = charG
many = manyG
orElse = orElseG
primitive s _ = primitiveG s
nonTerminal s g = nonTerminal s g
And we can now take the derived definitions, of which so far we had two copies, and define them once and for all:
many1 :: Descr f => f a -> f [a]
many1 p = pure (:) <*> p <*> many p
anyChar :: Descr f => f Char
anyChar = primitive "char" anyCharP
dottedWords :: Descr f => f [String]
dottedWords = many1 (many anyChar <* char '.')
sepBy :: Descr f => f a -> f () -> f [a]
sepBy p1 p2 = ((:) <$> p1 <*> (many (p2 *> p1))) orElse pure []
newline :: Descr f => f ()
newline = primitive "newline" (charP '\n')
And thus we now have our CSV parser/grammar generator:
parseCSV :: Descr f => f [[String]]
parseCSV = many parseLine
where
parseLine = nonTerminal "line" $
parseCell sepBy char ',' <* newline
parseCell = nonTerminal "cell" $
char '"' *> many (primitive "not-quote" (anyCharButP '"')) <* char '"'
We can now use this definition both to parse and to generate grammars:
*Main> putStr $ ppGrammar2 "csv" (parseCSV)
cell = '"', not-quote , '"';
line = (cell, ',', cell ''), newline;
csv = line ;
*Main> parse parseCSV "\"ab\",\"cd\"\n\"\",\"de\"\n\n"
Just [["ab","cd"],["","de"],[]]
parseINIP :: Parser INIFile
parseINIP = many1P parseSection
where
parseSection =
(,) <$ charP '['
<*> parseIdent
<* charP ']'
<* charP '\n'
<*> (catMaybes <$> manyP parseLine)
parseIdent = many1P letterOrDigitP
parseLine = parseDecl orElseP parseComment orElseP parseEmpty
parseDecl = Just <$> (
(,) <*> parseIdent
<* manyP (charP ' ')
<* charP '='
<* manyP (charP ' ')
<*> many1P (anyCharButP '\n')
<* charP '\n')
parseComment =
Nothing <$ charP '#'
<* many1P (anyCharButP '\n')
<* charP '\n'
parseEmpty = Nothing <$ charP '\n'
Transforming that to a generic description is quite straightforward. We use primitive again to wrap letterOrDigitP:
descrINI :: Descr f => f INIFile
descrINI = many1 parseSection
where
parseSection =
(,) <* char '['
<*> parseIdent
<* char ']'
<* newline
<*> (catMaybes <$> many parseLine)
parseIdent = many1 (primitive "alphanum" letterOrDigitP)
parseLine = parseDecl orElse parseComment orElse parseEmpty
parseDecl = Just <$> (
(,) <*> parseIdent
<* many (char ' ')
<* char '='
<* many (char ' ')
<*> many1 (primitive "non-newline" (anyCharButP '\n'))
<* newline)
parseComment =
Nothing <$ char '#'
<* many1 (primitive "non-newline" (anyCharButP '\n'))
<* newline
parseEmpty = Nothing <$ newline
This yields this not very helpful grammar (abbreviated here):
*Main> putStr $ ppGrammar2 "ini" descrINI
ini = '[', alphanum, alphanum , ']', newline, alphanum, alphanum , ' '
But with a few uses of nonTerminal, we get something really nice:
descrINI :: Descr f => f INIFile
descrINI = many1 parseSection
where
parseSection = nonTerminal "section" $
(,) <$ char '['
<*> parseIdent
<* char ']'
<* newline
<*> (catMaybes <$> many parseLine)
parseIdent = nonTerminal "identifier" $
many1 (primitive "alphanum" letterOrDigitP)
parseLine = nonTerminal "line" $
parseDecl orElse parseComment orElse parseEmpty
parseDecl = nonTerminal "declaration" $ Just <$> (
(,) <$> parseIdent
<* spaces
<* char '='
<* spaces
<*> remainder)
parseComment = nonTerminal "comment" $
Nothing <$ char '#' <* remainder
remainder = nonTerminal "line-remainder" $
many1 (primitive "non-newline" (anyCharButP '\n')) <* newline
parseEmpty = Nothing <$ newline
spaces = nonTerminal "spaces" $ many (char ' ')
*Main> putStr $ ppGrammar "ini" descrINI
identifier = alphanum, alphanum ;
spaces = ' ' ;
line-remainder = non-newline, non-newline , newline;
declaration = identifier, spaces, '=', spaces, line-remainder;
comment = '#', line-remainder;
line = declaration comment newline;
section = '[', identifier, ']', newline, line ;
ini = section, section ;
const = digit, digit ;
spaces = ' ' newline ;
atom = const '(', spaces, expr, spaces, ')', spaces;
mult = atom, spaces, '*', spaces, atom , spaces;
plus = mult, spaces, '+', spaces, mult , spaces;
expr = plus;
The production of expr is recursive (via plus, mult, atom). We have seen above that simply defining a Grammar a recursively does not go well.
One solution is to add a new combinator for explicit recursion, which replaces nonTerminal in the method:
class Applicative f => Descr f where
recNonTerminal :: String -> (f a -> f a) -> f a
instance Descr Parser where
recNonTerminal _ p = let r = p r in r
instance Descr Grammar where
recNonTerminal = recNonTerminalG
recNonTerminalG :: String -> (Grammar a -> Grammar a) -> Grammar a
recNonTerminalG name f =
let G (prods, rhs) = f (G ([], NonTerminal name))
in G (prods ++ [(name, rhs)], NonTerminal name)
nonTerminal :: Descr f => String -> f a -> f a
nonTerminal name p = recNonTerminal name (const p)
runGrammer :: String -> Grammar a -> BNF
runGrammer main (G (prods, NonTerminal nt)) main == nt = prods
runGrammer main (G (prods, rhs)) = prods ++ [(main, rhs)]
The change in runGrammer avoids adding a pointless expr = expr production to the output.
This lets us define a parser/grammar-generator for the arithmetic expressions given above:
data Expr = Plus Expr Expr Mult Expr Expr Const Integer
deriving Show
mkPlus :: Expr -> [Expr] -> Expr
mkPlus = foldl Plus
mkMult :: Expr -> [Expr] -> Expr
mkMult = foldl Mult
parseExpr :: Descr f => f Expr
parseExpr = recNonTerminal "expr" $ \ exp ->
ePlus exp
ePlus :: Descr f => f Expr -> f Expr
ePlus exp = nonTerminal "plus" $
mkPlus <$> eMult exp
<*> many (spaces *> char '+' *> spaces *> eMult exp)
<* spaces
eMult :: Descr f => f Expr -> f Expr
eMult exp = nonTerminal "mult" $
mkPlus <$> eAtom exp
<*> many (spaces *> char '*' *> spaces *> eAtom exp)
<* spaces
eAtom :: Descr f => f Expr -> f Expr
eAtom exp = nonTerminal "atom" $
aConst orElse eParens exp
aConst :: Descr f => f Expr
aConst = nonTerminal "const" $ Const . read <$> many1 digit
eParens :: Descr f => f a -> f a
eParens inner =
id <$ char '('
<* spaces
<*> inner
<* spaces
<* char ')'
<* spaces
And indeed, this works:
*Main> putStr $ ppGrammar "expr" parseExpr
const = digit, digit ;
spaces = ' ' newline ;
atom = const '(', spaces, expr, spaces, ')', spaces;
mult = atom, spaces, '*', spaces, atom , spaces;
plus = mult, spaces, '+', spaces, mult , spaces;
expr = plus;
recNonTerminal and explicitly passing around the recursive call (i.e. the exp in the example). To implement that we have to adjust our Grammar type as follows:
newtype Grammar a = G ([String] -> (BNF, RHS))
The idea is that the list of strings is those non-terminals that we are currently defining. So in nonTerminal, we check if the non-terminal to be introduced is currently in the process of being defined, and then simply ignore the body. This way, the recursion is stopped automatically:
nonTerminalG :: String -> (Grammar a) -> Grammar a
nonTerminalG name (G g) = G $ \seen ->
if name elem seen
then ([], NonTerminal name)
else let (prods, rhs) = g (name : seen)
in (prods ++ [(name, rhs)], NonTerminal name)
After adjusting the other primitives of Grammar (including the Functor and Applicative instances, wich now again have nonTerminal) to type-check again, we observe that this parser/grammar generator for expressions, with genuine recursion, works now:
parseExp :: Descr f => f Expr
parseExp = nonTerminal "expr" $
ePlus
ePlus :: Descr f => f Expr
ePlus = nonTerminal "plus" $
mkPlus <$> eMult
<*> many (spaces *> char '+' *> spaces *> eMult)
<* spaces
eMult :: Descr f => f Expr
eMult = nonTerminal "mult" $
mkPlus <$> eAtom
<*> many (spaces *> char '*' *> spaces *> eAtom)
<* spaces
eAtom :: Descr f => f Expr
eAtom = nonTerminal "atom" $
aConst orElse eParens parseExp
Note that the recursion is only going to work if there is at least one call to nonTerminal somewhere around the recursive calls. We still cannot implement many as naively as above.
identifier = letter, letter digit '-' ;
spaces = ' ' newline ;
quoted-char = non-quote-or-backslash '\\', '\\' '\\', '\'';
terminal = '\'', quoted-char , '\'', spaces;
non-terminal = identifier, spaces;
option = '[', spaces, rhs, spaces, ']', spaces;
repetition = ' ', spaces, rhs, spaces, ' ', spaces;
group = '(', spaces, rhs, spaces, ')', spaces;
atom = terminal non-terminal option repetition group;
sequence = atom, spaces, ',', spaces, atom , spaces;
choice = sequence, spaces, ' ', spaces, sequence , spaces;
rhs = choice;
production = identifier, spaces, '=', spaces, rhs, ';', spaces;
bnf = production, production ;
This grammar is set up so that the precedence of , and is correctly implemented: a , b c will parse as (a, b) c.
In this syntax for BNF, terminal characters are quoted, i.e. inside ' ', a ' is replaced by \' and a \ is replaced by \\ this is done by the function quote in ppRHS.
If you do this, you should able to round-trip with the pretty-printer, i.e. parse back what it wrote:
*Main> let bnf1 = runGrammer "expr" parseExpr
*Main> let bnf2 = runGrammer "expr" parseBNF
*Main> let f = Data.Maybe.fromJust . parse parseBNF. ppBNF
*Main> f bnf1 == bnf1
True
*Main> f bnf2 == bnf2
True
The last line is quite meta: We are using parseBNF as a parser on the pretty-printed grammar produced from interpreting parseBNF as a grammar.
Applicative, and not Monad. Applicative is less powerful, so by giving less power to the user of our Descr interface, the other side, i.e. the implementation, can be more powerful.
The reason why Applicative is ok, but Monad is not, is that in Applicative, the results do not affect the shape of the computation, whereas in Monad, the whole point of the bind operator (>>=) is that the result of the computation is used to decide the next computation. And while this is perfectly fine for a parser, it just makes no sense for a grammar generator, where there simply are no values around!
We have also seen that a phantom type, namely the parameter of Grammar, can be useful, as it lets the type system make sure we do not write nonsense. For example, the type of orElseG ensures that both grammars that are combined here indeed describe something of the same type.
Applicative yesterday. <*> and <* the > point out where the arguments are that are being passed to the function on the left.
After the Debian Desktop Team made the
call for proposing themes,
a total of twelve choices
have been submitted, and any Debian contributor has received
the opportunity to vote on them in a survey.
We received 3,479 responses ranking the different choices,
and softWaves has been the winner among them.
We'd like to thank all the designers that have participated
providing nice wallpapers and artwork for Debian 9,
and encourage everybody interested in this area of Debian,
to join the Design Team.
It is being considered
to package all of them so they are easily available in Debian.
If you want to help in this effort, or package any other artwork
(for example, particularly designed to be accessibility-friendly),
please contact the Debian Desktop Team, but hurry up,
because the freeze for new packages in the next release of Debian
starts on January 5th, 2017.
This is the second time that Debian ships a theme by Juliette Belin,
who also created the theme "Lines"
that enhances our actual stable release, Debian 8.
Congratulations, Juliette, and thank you very much for your continued commitment to Debian!
Given that I started the GR process, and that I called for discussion and votes, I feel somehow as my duty to also put a simple wrap-around to this process. Of course, I'll say many things already well-known to my fellow Debian people, but also non-debianers read this.
So, for further context, if you need to, please read my previous blog post, where I was about to send a call for votes. It summarizes the situation and proposals; you will find we had a nice set of messages in debian-vote@lists.debian.org during September; I have to thank all the involved parties, much specially to Ian Jackson, who spent a lot of energy summing up the situation and clarifying the different bits to everyone involved.
So, we held the vote; you can be interested in looking at the detailed vote statistics for the 235 correctly received votes, and most importantly, the results:
First of all, I'll say I'm actually surprised at the results, as I expected Ian's proposal (acknowledge difficulty; I actually voted this proposal as my top option) to win and mine (repeal previous GR) to be last; turns out, the winner option was Iain's (remain private). But all in all, I am happy with the results: As I said during the discussion, I was much disappointed with the results to the previous GR on this topic And, yes, it seems the breaking point was when many people thought the privacy status of posted messages was in jeopardy; we cannot really compare what I would have liked to have in said vote if we had followed the strategy of leaving the original resolution text instead of replacing it, but I believe it would have passed. In fact, one more surprise of this iteration was that I expected Further Discussion to be ranked higher, somewhere between the three explicit options. I am happy, of course, we got such an overwhelming clarity of what does the project as a whole prefer.
And what was gained or lost with this whole excercise? Well, if nothing else, we gain to stop lying. For over ten years, we have had an accepted resolution binding us to release the messages sent to debian-private given such-and-such-conditions... But never got around to implement it. We now know that debian-private will remain private... But we should keep reminding ourselves to use the list as little as possible.
For a project such as Debian, which is often seen as a beacon of doing the right thing no matter what, I feel being explicit about not lying to ourselves of great importance. Yes, we have the principle of not hiding our problems, but it has long been argued that the use of this list is not hiding or problems. Private communication can happen whenever you have humans involved, even if administratively we tried to avoid it.
Any of the three running options could have won, and I'd be happy. My #1 didn't win, but my #2 did. And, I am sure, it's for the best of the project as a whole.
$package_$architecture_$version.buildinfo file.
We anticipate the next dpkg upload to sid will create .buildinfo files by default. Whilst it's clear that we also need to teach dak to deal with them (#763822) its not actually clear how to handle .buildinfo files after dak has processed them and how to make them available to the world.
To this end, Chris Lamb has started development on a proof-of-concept .buildinfo server to see what issues arise. Source
Reproducible work in other projects
A little over eight years ago, I wrote a short blog post which somewhat dryly noted that I had completed the five marathons constituting the World Marathon Majors. I had completed Boston, Chicago and New York during 2007, adding London and then Berlin (with a personal best) in 2008. The World Marathon Majors existed then, but I was not aware of a website. The organisation was aiming to raise the profile of the professional and very high-end aspect of the sport. But marathoning is funny as they let somewhat regular folks like you and me into the same race. And I always wondered if someone kept track of regular folks completing the suite...
I have been running a little less the last few years, though I did get around to complete the Illinois Marathon earlier this year (only tweeted about it and still have not added anything to the running section of my blog). But two weeks ago, I was once again handing out water cups at the Chicago Marathon, sending along two tweets when the elite wheelchair and elite male runners flew by. To the first, the World Marathon Majors account replied, which lead me to their website. Which in turn lead me to the Five Star Finisher page, and the newer / larger Six Star Finisher page now that Tokyo has been added.
And in short, one can now request one's record to be added (if they check out). So I did. And now I am on the Five Star Finisher page!
I don't think I'll ever surpass that as a runner. The table header and my row look like this:
If only my fifth / sixth grade physical education teacher could see that---he was one of those early running nuts from the 1970s and made us run towards / around this (by now enlarged) pond and boy did I hate that :) Guess it did have some long lasting effects. And I casually circled the lake a few years ago, starting much further away from my parents place. Once you are in the groove for distance...
But leaving that aside, running has been fun and I with some luck I may have another one or two marathons or Ragnar Relays left. The only really bad part about this is that I may have to get myself to Tokyo after all (for something that is not an ISM workshop) ...
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. Please report excessive re-aggregation in third-party for-profit settings.
$ python3
>>> from monerowallet import MoneroWallet
>>> mw = MoneroWallet()
>>> mw.getbalance()
'unlocked_balance': 2262265030000, 'balance': 2262265030000
Lots of features are included, you should have a look at the documentation of the monerowallet module to know them all, but quickly here are some of them:
Feel free to contribute to this starting project to help spreading the Monero use by using the PyMoneroWallet project with your Python applications
Summary stats
=============
Fuzzers alive : 10
Total run time : 68 days, 7 hours
Total execs : 9268 million
Cumulative speed : 15698 execs/sec
Pending paths : 0 faves, 2501 total
Pending per fuzzer : 0 faves, 250 total (on average)
Crashes found : 9 locally unique
afl-cmin -i queue_all/ -o queue_cmin -- test_decode_svg @@ 1.0 /dev/null
corpus minimization tool for afl-fuzz by
[+] OK, 1447 tuples recorded.
[*] Obtaining traces for input files in 'queue_all/'...
Processing file 8242/8242...
[*] Sorting trace sets (this may take a while)...
[+] Found 23812 unique tuples across 8242 files.
[*] Finding best candidates for each tuple...
Processing file 8242/8242...
[*] Sorting candidate list (be patient)...
[*] Processing candidates and writing output files...
Processing tuple 23812/23812...
[+] Narrowed down to 2612 files, saved in 'queue_cmin'.
Thats So Raven
If I could sum up the past year in one word, that word would be distraction. There have been so many strange, confusing or simply unforseen things going on I have had trouble focusing like never before.
For instance, on the opposite side of the street from me is one of Jersey City's old resorvoirs. It's not used for drinking water anymore and the city eventually plans on merging it into the park on the other side. In the meantime it has become something of a wildlife refuge. Which is nice except one of the newly settled critters was a bird of prey -- the consensus is possibly some kind of hawk or raven. Starting your morning commute under the eyes of a harbinger of death is very goth and I even learned to deal with the occasional piece of deconstructed rodent on my doorstep but nighttime was a big problem. For contrary to popular belief, ravens do not quoth "nevermore" but "KRRAAAA". Very loudly. Just as soon as you have drifted of to sleep. Eventually my sleep-deprived neighbors and I appealed to the NJ division of enviromental protection to get it removed but by the time they were ready to swing into action the bird had left for somewhere more congenial like Transylvania or Newark.
Or here are some more complete wastes of time: I go the doctor for my annual physical. The insurance company codes it as Adult Onset Diabetes by accident. One day I opened the lid of my laptop and there's a "ping" sound and a piece of the hinge flies off. Apparently that also severed the connection to the screen and naturally the warranty had just expired so I had to spend the next month tethered to an external monitor until I could afford to buy a new one. Mix in all the usual social, political, family and work drama and you can see that it has been a very trying time for me.
Dovecot
I have managed to get some Debian work done. On Dovecot, my principal package, I have gotten tremendous support from Apollon Oikonomopolous who I belatedly welcome as a member of the Dovecot maintainer team. He has been particularly helpful in fixing our systemd support and cleaning out a lot of the old and invalid bugs. We're in pretty good shape for the freeze. Upstream has released an RC of 2.2.26 and hopefully the final version will be out in the next couple of days so we can include it in Stretch. We can always use more help with the package so let me know if you're interested.
Debian-IN
Most of the action has been going on without me but I've been lending support and sponsoring whenever I can. We have several new DDs and DMs but still no one north of the Vindhyas I'm afraid.
Debian Perl Group
gregoa did a ping of inactive maintainers and I regretfully had to admit to myself that I wasn't going to be of use anytime soon so I resigned. Perl remains my favorite language and I've actually been more involved in the meetings of my local Perlmongers group so hopefully I will be back again one day. And I still maintain the Perl modules I wrote myself.
Debian-Axe-Murderers*
May have gained a recruit.
*Stricly speaking it should be called Debian-People-Who-Dont-Think-Faults-in-One-Moral-Domain-Such-As-For-Example-Axe-Murdering-Should-Leak-Into-Another-Moral-Domain-Such-As-For-Example-Debian but come on, that's just silly.
make,
that would fail so hard that hundreds of lines of errors and warnings
were outputted. There are two main reasons doing this is a bad idea:
/var/log/, a web page on a local
server, anything. Learn where different software write logs on your
system, and integrate reading them in your workflow. Often, it will be
your only information about the bug. Often, it will tell you where the
bug lies. Sometimes, it will even give you hints on how to fix it.
You may have to filter out a lot of garbage to find relevant
information about your bug. Learn to spot some keywords like error
or warning. In long stacktraces, spot the lines concerning your
files; because more often, your code is to be blamed, rather than
deeper library code. grep the logs with relevant keywords. If you
have the option, colorize the output. Use tail -f to follow a file
getting updated. There are so many ways to grasp logs, so find what
works best with you and never forget to use it!
Print foobar
That one doesn't concern compilation errors (unless it's a Makefile
error, in that case this file is your code anyway).
When the program logs and output failed to give you where an error
occured (oh hi Segmentation fault!), and before having to dive into
a memory debugger or system trace tool, spot the portion of your
program that causes the bug and add in there some print
statements. You can either print("foo") and print("bar"), just to
know that your program reaches or not a certain place in your code, or
print(some_faulty_var) to get more insights on your program
state. It will give you precious information.
stderr >> "foo" >> endl;
my_db.connect(); // is this broken?
stderr >> "bar" >> endl;
my_db that is broken if you get foo and not bar on your
standard error.
(That is an hypothetical example. If you know something can break,
such as a database connection, then you should always enclose it in a
try/catch structure).
Isolate and reproduce the bug
This point is linked to the previous one. You may or may not have
isolated the line(s) causing the bug, but maybe the issue is not
always raised. It can depend on many other things: the program or
function parameters, the network status, the amount of memory
available, the decisions of the OS scheduler, the user rights on the
system or on some files, etc. More generally, any assumption you made
on any external dependency can appear to be wrong (even if it's right
99% of the time). According to the context, try to isolate the set of
conditions that trigger the bug. It can be as simple as "when there is
no internet connection", or as complicated as "when the CPU load of
some external machine is too high, it's a leap year, and the input
contains illegal utf-8 characters" (ok, that one is fucked up; but it
surely happens!). But you need to reliably be able to reproduce the
bug, in order to be sure later that you indeed fixed it.
Of course when the bug is triggered at every run, it can be
frustrating that your program never works but it will in general be
easier to fix.
RTFM
Always read the documentation before reaching out for help. Be it
man, a book, a website or a wiki, you will find precious information
there to assist you in using a language or a specific library. It can
be quite intimidating at first, but it's often organized the same
way. You're likely to find a search tool, an API reference, a
tutorial, and many examples. Compare your code against them. Check in
the FAQ, maybe your bug and its solution are already referenced there.
You'll rapidly find yourself getting used to the way documentation is
organized, and you'll be more and more efficient at finding instantly
what you need. Always keep the doc window open!
Google and Stack Overflow are your friends
Let's be honest: many of the bugs you'll encounter have been
encountered before. Learn to write efficient queries on search
engines, and use the knowledge you can find on questions&answers
forums like Stack Overflow. Read the answers and comments. Be wise
though, and never blindly copy and
paste
code from there. It can be as bad as introducing malicious security
issues into your code, and you won't learn anything. Oh, and don't
copy and paste anyway. You have to be sure you understand every single
line, so better write them by hand; it's also better for memorizing
the issue.
Take notes
Once you have identified and solved a particular bug, I advise to
write about it. No need for shiny interfaces: keep a list of your bugs
along with their solutions in one or many text files, organized by
language or framework, that you can easily grep.
It can seem slightly cumbersome to do so, but it proved (at least to
me) to be very valuable. I can often recall I have encountered some
buggy situation in the past, but don't always remember the
solution. Instead of losing all the debugging time again, I search in
my bug/solution list first, and when it's a hit I'm more than happy I
kept it.
Further print statement (at the price of being
over-engineered for simple programs): severity levels (info,
warning, error, fatal, etc), output in rotating files, and
many more.
As I posted yesterday,
we released PATHspider 1.0.0. What I didn t talk about in that post was an
event that occured only a few hours before the release.
Everything was going fine, proofreading of the documentation was in progress,
a quick git push with the documentation updates and CI FAILED!?! Our
CI doesn t build the documentation, only tests the core code. I m planning to
release real soon and something has broken.
Starting to panic.
irl@orbiter# ./tests.sh
................
----------------------------------------------------------------------
Ran 16 tests in 0.984s
OK
This makes no sense. Maybe I forgot to add a dependency and it s been broken
for a while? I scrutinise the dependencies list and it all looks fine.
In fairness, probably the first thing I should have done is look at the build
log in Jenkins,
but I ve never had a failure that I couldn t reproduce locally before.
It was at this point that I realised there was something screwy going on. A
sigh of relief as I realise that there s not a catastrophic test failure but
now it looks like maybe there s a problem with the University research group
network, which is arguably worse.
Being focussed on getting the release ready, I didn t realise that the Internet
was
falling
apart.
Unknown to me, a massive DDoS attack against Dyn, a major DNS host, was in
progress. After a few attempts to debug the problem, I hardcoded a line into
/etc/hosts, still believing it to be a localised issue.
192.30.253.112 github.com
I ve just removed this line as the problem seems to have resolved itself for
now. There are two main points I ve taken away from this:
Back in August, I was looking for a
Homeserver replacement. During FrOSCon I was then reminded
of the Turris Omnia project by NIC.cz. The basic SoC (Marvel
Armada 38x) seemed to be nice hand have decent mainline support (and,
with the turris, users interested in keeping it working). Only I don't
want any WIFI and I wasn't sure the standard case would be all that
usefully. Fortunately, there's also a simple board available with the
same SoC called ClearFog and so I got one of these (the Base
version). With shipping and the SSD (the only 2242 M.2 SSD with 250
GiB I could find, a ADATA SP600) it slightly exceeds the budget but
well.
When installing the machine, the obvious goal was to use mainline FOSS
components only if possible. Fortunately there's mainline kernel
support for the device as well as mainline U-Boot. First attempts
to boot from a micro SD card did not work out at all, both with
mainline U-Boot and the vendor version though. Turns out the eMMC
version of the board does not support any micro SD cards at all, a
fact that is documented but others failed to notice as well.
U-Boot
As the board does not come with any loader on eMMC and booting
directly from M.2 requires removing some resistors from the board, the
easiest way is using UART for booting. The vendor wiki has some shell
script wrapping an included C fragment to feed U-Boot to the device
but all that is really needed is U-Boot's kwboot utility. For some
reason the SPL didn't properly detect UART booting on my device (wrong
magic number) but patching the if (in arch-mvebu's spl.c) and always
assume UART boot is an easy way around.
The plan then was to boot a Debian armhf rootfs with a
defconfig kernel from USB stick. and install U-Boot and the rootfs
to eMMC from within that system. Unfortunately U-Boot seems to be
unable to talk to the USB3 port so no kernel loading from there. One
could probably make UART loading work but switching between screen for
serial console and xmodem seemed somewhat fragile and I never got it
working. However ethernet can be made to work, though you need to set
eth1addr to eth3addr (or just the right one of these) in U-Boot,
saveenv and reboot. After that TFTP works (but is somewhat slow).
eMMC
There's one last step required to allow U-Boot and Linux to access the
eMMC. eMMC is wired to the same PINs as the SD card would be. However
the SD card has an additional indicator pin showing whether a card is
present. You might be lucky inserting a dummy card into the slot or go
the clean route and remove the pin specification from the device tree.
--- a/arch/arm/dts/armada-388-clearfog.dts
+++ b/arch/arm/dts/armada-388-clearfog.dts
@@ -306,7 +307,6 @@
sdhci@d8000
bus-width = <4>;
- cd-gpios = <&gpio0 20 GPIO_ACTIVE_LOW>;
no-1-8-v;
pinctrl-0 = <&clearfog_sdhci_pins
&clearfog_sdhci_cd_pins>;
Next Up is flashing the U-Boot to eMMC. This seems to
work with the vendor U-Boot but proves to be tricky with
mainline. The fun part boils down to the fact that the boot firmware
reads the first block from eMMC, but the second from SD card. If
you write the mainline U-Boot, which was written and tested for SD
card, to eMMC the SPL will try to load the main U-Boot starting from
it's second sector from flash -- obviously resulting in garbage. This
one took me several tries to figure out and made me read most of the
SPL code for the device. The fix however is trivial (apart from the
question on how to support all different variants from one codebase,
which I'll leave to the U-Boot developers):
--- a/include/configs/clearfog.h
+++ b/include/configs/clearfog.h
@@ -143,8 +143,7 @@
#define CONFIG_SPL_LIBDISK_SUPPORT
#define CONFIG_SYS_MMC_U_BOOT_OFFS (160 << 10)
#define CONFIG_SYS_U_BOOT_OFFS CONFIG_SYS_MMC_U_BOOT_OFFS
-#define CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR ((CONFIG_SYS_U_BOOT_OFFS / 512)\
- + 1)
+#define CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR (CONFIG_SYS_U_BOOT_OFFS / 512)
#define CONFIG_SYS_U_BOOT_MAX_SIZE_SECTORS ((512 << 10) / 512) /* 512KiB */
#ifdef CONFIG_SPL_BUILD
#define CONFIG_FIXED_SDHCI_ALIGNED_BUFFER 0x00180000 /* in SDRAM */
Linux
Now we have a System booting from eMMC with mainline U-Boot (which is
a most welcome speedup compared to the UART and TFTP combination from
the beginning). Getting to fine-tune linux on the device -- we want to
install the armmp Debian kernel and have it work. As all the drivers
are build as modules for that kernel this also means initrd
support. Funnily U-Boots bootz allows booting a plain vmlinux
kernel but I couldn't get it to boot a plain initrd. Passing a uImage
initrd and a normal kernel however works pretty well. Back when I
first tried there were some modules missing and ethernet didn't work
with the PHY driver built as a module. In the meantime the PHY problem
was fixed in the Debian kernel and almost all modules already
added. Ben then only added the USB3 module on my suggestion and as a
result, unstable's armhf armmp kernel should work perfectly well on
the device (you still need to patch the device tree similar to the
patch above). Still missing is an updated flash-kernel to
automatically generate the initrd uImage which is
work in progress but got stalled until I fixed the U-Boot on eMMC
problem and everything should be fine -- maybe get debian u-boot
builds for that board.
Pro versus Base
The main difference so far between the Pro and the Base version of the
ClearFog is the switch chip which is included on the Pro. The Base
instead "just" has two gigabit ethernet ports and a SFP. Both, linux'
and U-Boot's device tree are intended for the Pro version which makes
on of the ethernet ports unusable (it tries to find the switch behind
the ethernet port which isn't there). To get both ports working (or
the one you settled on earlier) there's a second patch to the device
tree (my version might be sub-optimal but works), U-Boot -- the
linux-kernel version is a trivial adaption:
--- a/arch/arm/dts/armada-388-clearfog.dts
+++ b/arch/arm/dts/armada-388-clearfog.dts
@@ -89,13 +89,10 @@
internal-regs
ethernet@30000
mac-address = [00 50 43 02 02 02];
+ managed = "in-band-status";
+ phy = <&phy1>;
phy-mode = "sgmii";
status = "okay";
-
- fixed-link
- speed = <1000>;
- full-duplex;
- ;
;
ethernet@34000
@@ -227,6 +224,10 @@
pinctrl-0 = <&mdio_pins>;
pinctrl-names = "default";
+ phy1: ethernet-phy@1 /* Marvell 88E1512 */
+ reg = <1>;
+ ;
+
phy_dedicated: ethernet-phy@0
/*
* Annoyingly, the marvell phy driver
@@ -386,62 +386,6 @@
tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>;
;
- dsa@0
- compatible = "marvell,dsa";
- dsa,ethernet = <ð1>;
- dsa,mii-bus = <&mdio>;
- pinctrl-0 = <&clearfog_dsa0_clk_pins &clearfog_dsa0_pins>;
- pinctrl-names = "default";
- #address-cells = <2>;
- #size-cells = <0>;
-
- switch@0
- #address-cells = <1>;
- #size-cells = <0>;
- reg = <4 0>;
-
- port@0
- reg = <0>;
- label = "lan1";
- ;
-
- port@1
- reg = <1>;
- label = "lan2";
- ;
-
- port@2
- reg = <2>;
- label = "lan3";
- ;
-
- port@3
- reg = <3>;
- label = "lan4";
- ;
-
- port@4
- reg = <4>;
- label = "lan5";
- ;
-
- port@5
- reg = <5>;
- label = "cpu";
- ;
-
- port@6
- /* 88E1512 external phy */
- reg = <6>;
- label = "lan6";
- fixed-link
- speed = <1000>;
- full-duplex;
- ;
- ;
- ;
- ;
-
gpio-keys
compatible = "gpio-keys";
pinctrl-0 = <&rear_button_pins>;
Conclusion
Apart from the mess with eMMC this seems to be a pretty nice
device. It's now happily running with a M.2 SSD providing enough
storage for now and still has a mSATA/mPCIe plug left for future
journeys. It seems to be drawing around 5.5 Watts with SSD and one
Ethernet connected while mostly idle and can feed around 500 Mb/s from
disk over an encrypted ethernet connection which is, I guess, not too
bad. My plans now include helping to finish flash-kernel support,
creating a nice case and probably get it deployed. I might bring
it to FOSDEM first though.
Working on it was really quite some fun (apart from the frustrating
parts finding the one-block-offset ..) and people were really
helpful. Big thanks here to Debian's arm folks, Ben Hutchings the
kernel maintainer and U-Boot upstream (especially Tom Rini and Stefan
Roese)
A large part of the internet became inaccessible today after a botnet made up of IP cameras and digital video recorders was used to DoS a major DNS provider. This highlighted a bunch of things including how maybe having all your DNS handled by a single provider is not the best of plans, but in the long run there's no real amount of diversification that can fix this - malicious actors have control of a sufficiently large number of hosts that they could easily take out multiple providers simultaneously.Install and setup OBS server, api, worker and osc CLI packages# echo "deb http://httpredir.debian.org/debian experimental main" >> /etc/apt/sources.list.d/experimental.list # apt-get update
In the install process mysql database is needed, therefore if mysql server is not setup, a password needs to be provided.# apt-get install obs-server obs-api obs-worker osc
Accept dummy certificate and provide credentials (defaults: Admin/opensuse)$ osc -A https://stretch ls
Create an OBS project for Download on Demand (DoD) Create a meta project file:# backend services obsrun 813 0.0 0.9 104960 20448 ? Ss 08:33 0:03 /usr/bin/perl -w /usr/lib/obs/server/bs_dodup obsrun 815 0.0 1.5 157512 31940 ? Ss 08:33 0:07 /usr/bin/perl -w /usr/lib/obs/server/bs_repserver obsrun 1295 0.0 1.6 157644 32960 ? S 08:34 0:07 \_ /usr/bin/perl -w /usr/lib/obs/server/bs_repserver obsrun 816 0.0 1.8 167972 38600 ? Ss 08:33 0:08 /usr/bin/perl -w /usr/lib/obs/server/bs_srcserver obsrun 1296 0.0 1.8 168100 38864 ? S 08:34 0:09 \_ /usr/bin/perl -w /usr/lib/obs/server/bs_srcserver memcache 817 0.0 0.6 346964 12872 ? Ssl 08:33 0:11 /usr/bin/memcached -m 64 -p 11211 -u memcache -l 127.0.0.1 obsrun 818 0.1 0.5 78548 11884 ? Ss 08:33 0:41 /usr/bin/perl -w /usr/lib/obs/server/bs_dispatch obsserv+ 819 0.0 0.3 77516 7196 ? Ss 08:33 0:05 /usr/bin/perl -w /usr/lib/obs/server/bs_service mysql 851 0.0 0.0 4284 1324 ? Ss 08:33 0:00 /bin/sh /usr/bin/mysqld_safe mysql 1239 0.2 6.3 1010744 130104 ? Sl 08:33 1:31 \_ /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --log-error=/var/log/mysql/error.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306 # web services root 1452 0.0 0.1 110020 3968 ? Ss 08:34 0:01 /usr/sbin/apache2 -k start root 1454 0.0 0.1 435992 3496 ? Ssl 08:34 0:00 \_ Passenger watchdog root 1460 0.3 0.2 651044 5188 ? Sl 08:34 1:46 \_ Passenger core nobody 1465 0.0 0.1 444572 3312 ? Sl 08:34 0:00 \_ Passenger ust-router www-data 1476 0.0 0.1 855892 2608 ? Sl 08:34 0:09 \_ /usr/sbin/apache2 -k start www-data 1477 0.0 0.1 856068 2880 ? Sl 08:34 0:09 \_ /usr/sbin/apache2 -k start www-data 1761 0.0 4.9 426868 102040 ? Sl 08:34 0:29 delayed_job.0 www-data 1767 0.0 4.8 425624 99888 ? Sl 08:34 0:30 delayed_job.1 www-data 1775 0.0 4.9 426516 101708 ? Sl 08:34 0:28 delayed_job.2 nobody 1788 0.0 5.7 496092 117480 ? Sl 08:34 0:03 Passenger RubyApp: /usr/share/obs/api nobody 1796 0.0 4.9 488888 102176 ? Sl 08:34 0:00 Passenger RubyApp: /usr/share/obs/api www-data 1814 0.0 4.5 282576 92376 ? Sl 08:34 0:22 delayed_job.1000 www-data 1829 0.0 4.4 282684 92228 ? Sl 08:34 0:22 delayed_job.1010 www-data 1841 0.0 4.5 282932 92536 ? Sl 08:34 0:22 delayed_job.1020 www-data 1855 0.0 4.9 427988 101492 ? Sl 08:34 0:29 delayed_job.1030 www-data 1865 0.2 5.0 492500 102964 ? Sl 08:34 1:09 clockworkd.clock www-data 1899 0.0 0.0 87100 1400 ? S 08:34 0:00 /usr/bin/searchd --pidfile --config /usr/share/obs/api/config/production.sphinx.conf www-data 1900 0.1 0.4 161620 8276 ? Sl 08:34 0:51 \_ /usr/bin/searchd --pidfile --config /usr/share/obs/api/config/production.sphinx.conf # OBS worker root 1604 0.0 0.0 28116 1492 ? Ss 08:34 0:00 SCREEN -m -d -c /srv/obs/run/worker/boot/screenrc root 1605 0.0 0.9 75424 18764 pts/0 Ss+ 08:34 0:06 \_ /usr/bin/perl -w ./bs_worker --hardstatus --root /srv/obs/worker/root_1 --statedir /srv/obs/run/worker/1 --id stretch:1 --reposerver http://obs:5252 --jobs 1
$ osc -A https://stretch:443 meta prj Debian:8 -e
<project name= Debian:8 >Visit webUI to check project configuration Create a meta project configuration file:
<title>Debian 8 DoD</title>
<description>Debian 8 DoD</description>
<person userid= Admin role= maintainer />
<repository name= main >
<download arch= x86_64 url= http://deb.debian.org/debian/jessie/main repotype= deb />
<arch>x86_64</arch>
</repository>
</project>
Add the following file, as found at build.opensuse.org$ osc -A https://stretch:443 meta prjconf Debian:8 -e
Visit webUI to check project configuration Create an OBS project linked to DoDRepotype: debian # create initial user Preinstall: base-passwd Preinstall: user-setup # required for preinstall images Preinstall: perl # preinstall essentials + dependencies Preinstall: base-files base-passwd bash bsdutils coreutils dash debconf Preinstall: debianutils diffutils dpkg e2fslibs e2fsprogs findutils gawk Preinstall: gcc-4.9-base grep gzip hostname initscripts insserv libacl1 Preinstall: libattr1 libblkid1 libbz2-1.0 libc-bin libc6 libcomerr2 libdb5.3 Preinstall: libgcc1 liblzma5 libmount1 libncurses5 libpam-modules Preinstall: libpcre3 libsmartcols1 Preinstall: libpam-modules-bin libpam-runtime libpam0g libreadline6 Preinstall: libselinux1 libsemanage-common libsemanage1 libsepol1 libsigsegv2 Preinstall: libslang2 libss2 libtinfo5 libustr-1.0-1 libuuid1 login lsb-base Preinstall: mount multiarch-support ncurses-base ncurses-bin passwd perl-base Preinstall: readline-common sed sensible-utils sysv-rc sysvinit sysvinit-utils Preinstall: tar tzdata util-linux zlib1g Runscripts: base-passwd user-setup base-files gawk VMinstall: libdevmapper1.02.1 Order: user-setup:base-files # Essential packages (this should also pull the dependencies) Support: base-files base-passwd bash bsdutils coreutils dash debianutils Support: diffutils dpkg e2fsprogs findutils grep gzip hostname libc-bin Support: login mount ncurses-base ncurses-bin perl-base sed sysvinit Support: sysvinit-utils tar util-linux # Build-essentials Required: build-essential Prefer: build-essential:make # build script needs fakeroot Support: fakeroot # lintian support would be nice, but breaks too much atm #Support: lintian # helper tools in the chroot Support: less kmod net-tools procps psmisc strace vim # everything below same as for Debian:6.0 (apart from the version macros ofc) # circular dependendencies in openjdk stack Order: openjdk-6-jre-lib:openjdk-6-jre-headless Order: openjdk-6-jre-headless:ca-certificates-java Keep: binutils cpp cracklib file findutils gawk gcc gcc-ada gcc-c++ Keep: gzip libada libstdc++ libunwind Keep: libunwind-devel libzio make mktemp pam-devel pam-modules Keep: patch perl rcs timezone Prefer: cvs libesd0 libfam0 libfam-dev expect Prefer: gawk locales default-jdk Prefer: xorg-x11-libs libpng fam mozilla mozilla-nss xorg-x11-Mesa Prefer: unixODBC libsoup glitz java-1_4_2-sun gnome-panel Prefer: desktop-data-SuSE gnome2-SuSE mono-nunit gecko-sharp2 Prefer: apache2-prefork openmotif-libs ghostscript-mini gtk-sharp Prefer: glib-sharp libzypp-zmd-backend mDNSResponder Prefer: -libgcc-mainline -libstdc++-mainline -gcc-mainline-c++ Prefer: -libgcj-mainline -viewperf -compat -compat-openssl097g Prefer: -zmd -OpenOffice_org -pam-laus -libgcc-tree-ssa -busybox-links Prefer: -crossover-office -libgnutls11-dev # alternative pkg-config implementation Prefer: -pkgconf Prefer: -openrc Prefer: -file-rc Conflict: ghostscript-library:ghostscript-mini Ignore: sysvinit:initscripts Ignore: aaa_base:aaa_skel,suse-release,logrotate,ash,mingetty,distribution-release Ignore: gettext-devel:libgcj,libstdc++-devel Ignore: pwdutils:openslp Ignore: pam-modules:resmgr Ignore: rpm:suse-build-key,build-key Ignore: bind-utils:bind-libs Ignore: alsa:dialog,pciutils Ignore: portmap:syslogd Ignore: fontconfig:freetype2 Ignore: fontconfig-devel:freetype2-devel Ignore: xorg-x11-libs:freetype2 Ignore: xorg-x11:x11-tools,resmgr,xkeyboard-config,xorg-x11-Mesa,libusb,freetype2,libjpeg,libpng Ignore: apache2:logrotate Ignore: arts:alsa,audiofile,resmgr,libogg,libvorbis Ignore: kdelibs3:alsa,arts,pcre,OpenEXR,aspell,cups-libs,mDNSResponder,krb5,libjasper Ignore: kdelibs3-devel:libvorbis-devel Ignore: kdebase3:kdebase3-ksysguardd,OpenEXR,dbus-1,dbus-1-qt,hal,powersave,openslp,libusb Ignore: kdebase3-SuSE:release-notes Ignore: jack:alsa,libsndfile Ignore: libxml2-devel:readline-devel Ignore: gnome-vfs2:gnome-mime-data,desktop-file-utils,cdparanoia,dbus-1,dbus-1-glib,krb5,hal,libsmbclient,fam,file_alteration Ignore: libgda:file_alteration Ignore: gnutls:lzo,libopencdk Ignore: gnutls-devel:lzo-devel,libopencdk-devel Ignore: pango:cairo,glitz,libpixman,libpng Ignore: pango-devel:cairo-devel Ignore: cairo-devel:libpixman-devel Ignore: libgnomeprint:libgnomecups Ignore: libgnomeprintui:libgnomecups Ignore: orbit2:libidl Ignore: orbit2-devel:libidl,libidl-devel,indent Ignore: qt3:libmng Ignore: qt-sql:qt_database_plugin Ignore: gtk2:libpng,libtiff Ignore: libgnomecanvas-devel:glib-devel Ignore: libgnomeui:gnome-icon-theme,shared-mime-info Ignore: scrollkeeper:docbook_4,sgml-skel Ignore: gnome-desktop:libgnomesu,startup-notification Ignore: python-devel:python-tk Ignore: gnome-pilot:gnome-panel Ignore: gnome-panel:control-center2 Ignore: gnome-menus:kdebase3 Ignore: gnome-main-menu:rug Ignore: libbonoboui:gnome-desktop Ignore: postfix:pcre Ignore: docbook_4:iso_ent,sgml-skel,xmlcharent Ignore: control-center2:nautilus,evolution-data-server,gnome-menus,gstreamer-plugins,gstreamer,metacity,mozilla-nspr,mozilla,libxklavier,gnome-desktop,startup-notification Ignore: docbook-xsl-stylesheets:xmlcharent Ignore: liby2util-devel:libstdc++-devel,openssl-devel Ignore: yast2:yast2-ncurses,yast2-theme-SuSELinux,perl-Config-Crontab,yast2-xml,SuSEfirewall2 Ignore: yast2-core:netcat,hwinfo,wireless-tools,sysfsutils Ignore: yast2-core-devel:libxcrypt-devel,hwinfo-devel,blocxx-devel,sysfsutils,libstdc++-devel Ignore: yast2-packagemanager-devel:rpm-devel,curl-devel,openssl-devel Ignore: yast2-devtools:perl-XML-Writer,libxslt,pkgconfig Ignore: yast2-installation:yast2-update,yast2-mouse,yast2-country,yast2-bootloader,yast2-packager,yast2-network,yast2-online-update,yast2-users,release-notes,autoyast2-installation Ignore: yast2-bootloader:bootloader-theme Ignore: yast2-packager:yast2-x11 Ignore: yast2-x11:sax2-libsax-perl Ignore: openslp-devel:openssl-devel Ignore: java-1_4_2-sun:xorg-x11-libs Ignore: java-1_4_2-sun-devel:xorg-x11-libs Ignore: kernel-um:xorg-x11-libs Ignore: tetex:xorg-x11-libs,expat,fontconfig,freetype2,libjpeg,libpng,ghostscript-x11,xaw3d,gd,dialog,ed Ignore: yast2-country:yast2-trans-stats Ignore: susehelp:susehelp_lang,suse_help_viewer Ignore: mailx:smtp_daemon Ignore: cron:smtp_daemon Ignore: hotplug:syslog Ignore: pcmcia:syslog Ignore: avalon-logkit:servlet Ignore: jython:servlet Ignore: ispell:ispell_dictionary,ispell_english_dictionary Ignore: aspell:aspel_dictionary,aspell_dictionary Ignore: smartlink-softmodem:kernel,kernel-nongpl Ignore: OpenOffice_org-de:myspell-german-dictionary Ignore: mediawiki:php-session,php-gettext,php-zlib,php-mysql,mod_php_any Ignore: squirrelmail:mod_php_any,php-session,php-gettext,php-iconv,php-mbstring,php-openssl Ignore: simias:mono(log4net) Ignore: zmd:mono(log4net) Ignore: horde:mod_php_any,php-gettext,php-mcrypt,php-imap,php-pear-log,php-pear,php-session,php Ignore: xerces-j2:xml-commons-apis,xml-commons-resolver Ignore: xdg-menu:desktop-data Ignore: nessus-libraries:nessus-core Ignore: evolution:yelp Ignore: mono-tools:mono(gconf-sharp),mono(glade-sharp),mono(gnome-sharp),mono(gtkhtml-sharp),mono(atk-sharp),mono(gdk-sharp),mono(glib-sharp),mono(gtk-sharp),mono(pango-sharp) Ignore: gecko-sharp2:mono(glib-sharp),mono(gtk-sharp) Ignore: vcdimager:libcdio.so.6,libcdio.so.6(CDIO_6),libiso9660.so.4,libiso9660.so.4(ISO9660_4) Ignore: libcdio:libcddb.so.2 Ignore: gnome-libs:libgnomeui Ignore: nautilus:gnome-themes Ignore: gnome-panel:gnome-themes Ignore: gnome-panel:tomboy Substitute: utempter %ifnarch s390 s390x ppc ia64 Substitute: java2-devel-packages java-1_4_2-sun-devel %else %ifnarch s390x Substitute: java2-devel-packages java-1_4_2-ibm-devel %else Substitute: java2-devel-packages java-1_4_2-ibm-devel xorg-x11-libs-32bit %endif %endif Substitute: yast2-devel-packages docbook-xsl-stylesheets doxygen libxslt perl-XML-Writer popt-devel sgml-skel update-desktop-files yast2 yast2-devtools yast2-packagemanager-devel yast2-perl-bindings yast2-testsuite # # SUSE compat mappings # Substitute: gcc-c++ gcc Substitute: libsigc++2-devel libsigc++-2.0-dev Substitute: glibc-devel-32bit Substitute: pkgconfig pkg-config %ifarch %ix86 Substitute: kernel-binary-packages kernel-default kernel-smp kernel-bigsmp kernel-debug kernel-um kernel-xen kernel-kdump %endif %ifarch ia64 Substitute: kernel-binary-packages kernel-default kernel-debug %endif %ifarch x86_64 Substitute: kernel-binary-packages kernel-default kernel-smp kernel-xen kernel-kdump %endif %ifarch ppc Substitute: kernel-binary-packages kernel-default kernel-kdump kernel-ppc64 kernel-iseries64 %endif %ifarch ppc64 Substitute: kernel-binary-packages kernel-ppc64 kernel-iseries64 %endif %ifarch s390 Substitute: kernel-binary-packages kernel-s390 %endif %ifarch s390x Substitute: kernel-binary-packages kernel-default %endif %define debian_version 800 Macros: %debian_version 800
$ osc -A https://stretch:443 meta prj test -e
<project name= test >Visit webUI to check project configuration Adding a package to the project
<title>test</title>
<description>test</description>
<person userid= Admin role= maintainer />
<repository name= Debian_8.0 >
<path project= Debian:8 repository= main />
<arch>x86_64</arch>
</repository>
</project>
$ osc -A https://stretch:443 co test ; cd test $ mkdir hello ; cd hello ; apt-get source -d hello ; cd - ; $ osc add hello $ osc ci -m "New import" helloThe package should go to dispatched state then get in blocked state while it downloads build dependencies from DoD link, eventually it should start building. Please check the journal logs to check if something went wrong or gets stuck. Visit webUI to check hello package build state OBS logging to the journal Check in the journal logs everything went fine:
$ sudo journalctl -u obsdispatcher.service -u obsdodup.service -u obsscheduler@x86_64.service -u obsworker.service -u obspublisher.serviceTroubleshooting Currently we are facing few issues with web UI:
Choice of smart card
For standard PGP use, the OpenPGP card provides a good choice.
For X.509 use cases, such as VPN access, there are a range of choices. I recently obtained one of the SmartCard HSM cards, Card Contact were kind enough to provide me with a free sample. An interesting feature of this card is Elliptic Curve (ECC) support. More potential cards are listed on the OpenSC page here.
Choice of card reader
The technical factors to consider are most easily explained with a table:
| On disk | Smartcard reader without PIN-pad | Smartcard reader with PIN-pad | |
|---|---|---|---|
| Software | Free/open | Mostly free/open, Proprietary firmware in reader | |
| Key extraction | Possible | Not generally possible | |
| Passphrase compromise attack vectors | Hardware or software keyloggers, phishing, user error (unsophisticated attackers) | Exploiting firmware bugs over USB (only sophisticated attackers) | |
| Other factors | No hardware | Small, USB key form-factor | Largest form factor |
although the software experience will be much the same if lots of individual card readers or USB flash drives are used.
Other devices
One additional idea that comes to mind is a hardware random number generator (TRNG), such as the FST-01.
Can you help with ideas or donations?
If you have any specific suggestions for hardware or can help arrange any donations of hardware for Outreachy interns, please come and join us in the pki-clean-room mailing list or consider adding ideas on the PGP / PKI clean room wiki.
Next.