Friday, 07 June 2019
|
Écrit par
Grégory Soutadé

Thym en fleur

Quoi de mieux pour occuper un vendredi de l'ascension qu'une petite balade sur les hauteurs de Grasse ? Comme en Décembre dernier, l'objectif est le radar de l'aviation civile (Haut-Montet) situé à 1335m d'altitude. Cette fois, il n'est plus question de suivre la conduite de gaz, mais de faire le détour par le col du Clapier. Autre changement : le point de départ qui se fait désormais à partir des terrains de tennis, juste au dessus d'altitude 500. Le parcours est donc porté à un peu plus de 10km aller et autant pour le retour.

Fleur

Début de la randonnée sur les coups de 10h afin de profiter encore un peu de la fraîcheur matinale (~20°C). Le départ est le même que pour le tour de la Marbrière, autant dire que l'on grimpe pas mal sur les premiers kilomètres.

Une fois arrivé tout en haut, il ne faut pas bifurquer vers la Marbrière, mais suivre le chemin principal qui débouche sur le vallon de St Christophe.

Ne pas bifurquer vers la Marbrière

La partie suivante est moins intéressante puisqu'il faut suivre la route sur 1km avant de prendre le chemin forestier en direction du col du Clapier.

Bifurquer vers le col du Clapier Chemin forestier

C'est alors que l'on rejoint la conduite de gaz. Attention à bien poursuivre sur le sentier balisé blanc et rouge et non sur le chemin de la conduite.

Vers la conduite de gaz Les antennes

Vallon St Christophe Continuer sur le chemin balisé

La sortie se fait juste avant la GAEC de la Malle. Poursuivre dans cette direction et prendre le sentier qui la contourne (sur la droite) avant le portail principal sans trop prêter attention aux panneaux d'interdiction d'entrée.

Le plateau de la Malle, d'en bas

Un peu plus loin, il faut tourner sur la gauche en direction du col du Clapier.

Ascension du col du Clapier Ascension du col du Clapier

Vue sur la mer

Au fur et à mesure de l'ascension, le superbe plateau de la Malle se dévoile et même à une centaine de mètres, l'odeur des ovins picote le nez.

Plateau de la Malle

Herbes sèches battues par le vent, thym en fleur. Le comité d'accueil du Clapier est splendide.

Herbes sèches et thym Fleur

Depuis les 1257m d'altitude, on a une vue époustouflante sur le plateau de la Malle, celui de Caussols et celui (au loin) de Calern. Ce détour se révèle au final plus intéressant que le chemin de la conduite de gaz ! On pourra aller explorer les quelques bergeries en ruine.

Plateau de Caussol et Calern

La suite se fait en direction du Haut-Montet, soit par la gauche (chemin balisé en jaune), soit en suivant le bord de la falaise sur la droite. L'idéal est de passer par le second à l'aller même s'il est moins évident.

Traverse du col du Clapier

Retour sur le chemin principal de la conduite. Cette fois, c'est tout droit.

Sans traîner, mais sans courir non plus, il faut bien compter ~2h30 pour atteindre le radar. Après quelques photos, il est temps de faire une pause bien méritée avec un bon casse-croûte.

Le radar du Haut-Montet

Le temps est assez brumeux sur la côte, dommage.

Depuis le Haut-Montet

Le retour suit le même chemin (sauf traverse depuis le col du Clapier), mais est plus rapide (~2h10).

Chemin balisé jaune

Pour l'anecdote, la semelle de ma chaussure droite, sûrement sous le charme du paysage, a décidé de rester sur place à mi-chemin, autant dire que les descentes dans les cailloux avec seulement une mini-semelle s'est révélée plutôt désagréable !

Tuesday, 21 May 2019
|
Écrit par
Grégory Soutadé

Parapluie rose

Couleurs des îles, parfums exotiques. Voilà la promesse de cette édition 2019. D'un point de vue décoration de la ville, le thème a visiblement moins inspiré le comité d'organisation que lors des deux éditions précédentes. Néanmoins, en ce qui concerne les animations, le public n'était pas en reste avec (entre autres) du théâtre, de la danse, un défilé de mode ainsi que ses traditionnels stands sur le cours Honoré Cresp. D'autant plus que le temps, certes changeant, aura été clément.

Une fois passé les portes de la villa Fragonard, l'émerveillement est toujours au rendez-vous devant cette capacité des rosiéristes à créer chaque année de nouveaux styles et couleurs ! J'ai été hélas un peu déçu par l'objectif de mon Canon EOS M10 qui n'a pas réussit à rendre des photos aussi belles que la réalité, il a eu beaucoup de mal à gérer la forte luminosité de la journée.

Sur le cours :

Rose blanche, col rouge

Rose orange, Grasse en arrière plan

Rose violette Roses violet clair

Rose moutarde

Rose blanche, liseret rose Rose jaune, liseret rouge

Rose jaune, liseret rouge

Rose blanche, coeur rosé

Quelques clichés du concours :

Roses roses Rose jaune, liseret orange

Roses blanche, liseret rose Roses blanches et roses

Rose blanche, coeur saumon Rose blanche, coeur rose

Roses rouges Roses rouges

Roses roses Roses roses

Roses tachetées Roses roses

Roses blanches, coeur rose Roses blanches, coeur rose

Rosier de la villa Fragonard

Rose blanche, col rouge Rose blanche, col rouge, abeille

Rose jaune

Statue de la place aux Aires

Wednesday, 10 April 2019
|
Écrit par
Grégory Soutadé

Another day, another script. This one helps to compute the maximum stack usage of a C program. In facts, it combines the output of cflow and GCC GNAT to find the heaviest path used (which is not necessary the deepest). The first one compute target software call graph while option -fstack-usage of GCC creates .su files containing stack usage of all functions.

Targets software are simple embedded software. This script is a simple base not intended to run on all cases, handle dynamic stack nor recursive functions (if you wish to add it...).

A file version is available here.

#!/usr/bin/env python

import os
import re
import argparse

class SUInfo:
    def __init__(self, filename, line, func_name, stack_size):
        self.filename = filename
        self.line = line
        self.func_name = func_name
        self.stack_size = stack_size

    def __str__(self):
        s = '%s() <%s:%s> %d' % (self.func_name, self.filename, self.line, self.stack_size)
        return s

class FlowElement:
    def __init__(self, root, depth, stack_size, suinfo):
        self.root = root
        self.depth = depth
        self.stack_size = stack_size
        self.suinfo = suinfo
        self.childs = []

    def append(self, suinfo):
        self.childs.append(suinfo)

    def __str__(self):
        spaces = '    ' * self.depth
        su = self.suinfo
        res = '%s-> %s() %d <%s:%d>' % (spaces, su.func_name, su.stack_size,
                                        su.filename, su.line)
        return res

def display_max_path(element):
    print('Max stack size %d' % (element.stack_size))
    print('Max path :')
    res = ''
    while element:
        res = str(element) + '\n' + res
        element = element.root
    print(res)

cflow_re = re.compile(r'([ ]*).*\(\) \<.* at (.*)\>:')

def parse_cflow_file(path, su_dict):
    root = None
    cur_root = None
    current = None
    cur_depth = 0
    max_stack_size = 0
    max_path = None
    with open(path) as f:
        while True:
            line = f.readline()
            if not line: break
            match = cflow_re.match(line)
            if not match: continue

            spaces = match.group(1)
            # Convert tab into 4 spaces
            spaces = spaces.replace('\t', '    ')
            depth = len(spaces)/4
            filename = match.group(2)
            (filename, line) = filename.split(':')
            filename = '%s:%s' % (os.path.basename(filename), line)

            suinfo = su_dict.get(filename, None)
            # Some functions may have been inlined
            if not suinfo:
                # print('WARNING: Key %s not found in su dict"' % (filename))
                continue

            if not root:
                root = FlowElement(None, 0, suinfo.stack_size, suinfo)
                cur_root = root
                current = root
                max_path = root
                max_stack_size = suinfo.stack_size
            else:
                # Go back
                if depth < cur_depth:
                    while cur_root.depth > (depth-1):
                        cur_root = cur_root.root
                # Go depth
                elif depth > cur_depth:
                    cur_root = current
                cur_depth = depth
                stack_size = cur_root.stack_size + suinfo.stack_size
                element = FlowElement(cur_root, cur_depth,
                                      stack_size,
                                      suinfo)
                current = element
                if stack_size > max_stack_size:
                    max_stack_size = stack_size
                    max_path = current
                cur_root.append(element)
    display_max_path(max_path)

su_re = re.compile(r'(.*)\t([0-9]+)\t(.*)')

def parse_su_files(path, su_dict):
    for root, dirs, files in os.walk(path):
        for sufile in files:
            if sufile[-2:] != 'su': continue
            with open(os.path.join(path, sufile)) as f:
                while True:
                    line = f.readline()
                    if not line: break
                    match = su_re.match(line)
                    if not match:
                        # print('WARNING no match for "%s"' % (line))
                        continue
                    infos = match.group(1)
                    (filename, line, size, function) = infos.split(':')
                    stack_size = int(match.group(2))
                    key = '%s:%s' % (filename, line)
                    su_info = SUInfo(filename, int(line), function, stack_size)
                    su_dict[key] = su_info


if __name__ == '__main__':
    optparser = argparse.ArgumentParser(description='Max static stack size computer')
    optparser.add_argument('-f', '--cflow-file', dest='cflow_file',
                           help='cflow generated file')
    optparser.add_argument('-d', '--su-dir', dest='su_dir',
                           default='.',
                           help='Directory where GNAT .su files are generated')
    options = optparser.parse_args()

    su_dict = {}

    parse_su_files(options.su_dir, su_dict)
    parse_cflow_file(options.cflow_file, su_dict)

Usage & example

Let's take this simple software as example.

First, compile your software using -fstack-usage options in CFLAGS. It will creates an .su file for each object file. Then, launch cflow on your software. Finally, call my script.

mkdir test
cd test
gcc -fstack-usage gget.c -lpthread -lcurl
cflow gget.c > cflow.res
./cflow.py -f cflow.res

Result:

Max stack size 608
Max path :
-> main() 352 <gget.c:493>
    -> do_transfert() 160 <gget.c:228>
        -> progress_cb() 96 <gget.c:214>
Friday, 05 April 2019
|
Écrit par
Grégory Soutadé

Affiche de la course

Après une année de disette, re voilà La foulée des baous organisée par l'association des entreprises et des commerçants Gaudois pour soutenir l'association "Ensemble avec Benoît" dans le but de récolter des fonds, sensibiliser, mais également d'enregistrer de nouveau donneurs potentiels pour lutter contre les leucémies. Le tout dans une ambiance très conviviale et avec un plateau d'intervenants fournit.

Les personnes possédants un certificat médical (même les enfants) pourront s'adonner à une de ces 4 épreuves :

  • Marche solidaire
  • 4.5km
  • 10km
  • Courses enfant (400m, 800m et 1600m)

Les tarifs sont de 12€ et 14€ (+5€ le jour même) pour les deux courses principales.

Seul petit bémol, la course est organisée le dimanche 21 avril 2019 ... dimanche de Pâques. Espérons que cela ne rebutera pas les futurs participants !

Retour : 343 participants ont fait le déplacement en ce dimanche de Pâques. C'est bien, mais on reste loin des 571 de l'édition précédente. Il faut dire que les prévisions météorologiques n'étaient pas les meilleures. Pourtant, avec un ciel couvert, une légère brise et 18°C, les conditions de course étaient idéales. Les personnes présentes auront pu échanger quelques mots avec le parrain de cette année (qui n'a pas assez été mis en avant) : Stéphane Diagana. On aurait aimé le voir sur la course, malheureusement il était en pleine préparation. Malgré tout, l'ambiance est toujours aussi conviviale du départ jusqu'à l'arrivée et même au-delà. Les heureux coureurs du 10km, ont eu droit à 3 ensembles musicaux : percussions, Provençal et symphonique pour égayer un parcours toujours aussi exigeant. Un grand merci à eux ainsi qu'aux nombreux bénévoles présents tout le long du parcours. Il y a deux ans, je n'avais couru qu'au chronomètre (sans GPS), cette année, j'ai pu enregistrer le profil de la course. Il est finalement assez simple : 3km de descente, 3km de côte, 2km de descente et 2km de côte, avec un final toujours aussi raide ! À ce titre, il faudra particulièrement féliciter les 5 membres de l'équipe All Fitness qui a parcouru les 10km en tractant un chariot pour permettre à une personne handicapée de faire la course. D'un point de vue strictement personnel, j'ai gagné 1"52 en 49"50 et me classe en 62e position.

Tuesday, 02 April 2019
|
Écrit par
Grégory Soutadé

It's now one year I use Let's Encrypt TLS wildcard certificates. Until now, all was fine, but since the beginning of 2019, there is two domains on my certificate : soutade.fr and *.soutade.fr and (maybe due to my certificate generation) I need to perform two challenges for renewal : HTTP (http01) and DNS (dns01).

So, I wrote a Python script that performs both :

#!/usr/bin/env python3
#-*- encoding: utf-8 -*-

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

#
# Handle certificate renewal using HTTP and DNS challenges
# DNS challenge performed by Gandi Live v5 API
#

import requests
import os
import argparse
import shutil

# Config
API_KEY = "YOUR-KEY"
LIVEDNS_API = "https://dns.api.gandi.net/api/v5/"
ACME_RECORD = '_acme-challenge'
ACME_CHALLENGE_PATH = '/var/www/.well-known/acme-challenge'

headers = {
    'X-Api-Key': API_KEY,
}

CERTBOT_TOKEN = os.environ.get('CERTBOT_TOKEN', None)
CERTBOT_VALIDATION = os.environ.get('CERTBOT_VALIDATION', None)
DOMAIN = os.environ.get('CERTBOT_DOMAIN', None)

optparser = argparse.ArgumentParser(description='Letsencrypt challenge for Gandi v5 API')
optparser.add_argument('-c', '--cleanup', dest='cleanup',
                       action="store_true", default=False,
                       help='Cleanup chanllenge')

options = optparser.parse_args()     

if options.cleanup:
    print('Cleanup')
    if os.path.exists(ACME_CHALLENGE_PATH):
        shutil.rmtree(ACME_CHALLENGE_PATH)
else:
    if CERTBOT_TOKEN and CERTBOT_VALIDATION:
        print('Build HTTP authentication')
        # Create token file for web server
        if not os.path.exists(ACME_CHALLENGE_PATH):
            os.makedirs(ACME_CHALLENGE_PATH)
        token_path = os.path.join(ACME_CHALLENGE_PATH, CERTBOT_TOKEN)

        with open(token_path, 'w') as token:
            token.write(CERTBOT_VALIDATION)
        exit(0)

response = requests.get(LIVEDNS_API + "zones", headers=headers)

target_zone = None
if (response.ok):
    zones = response.json()
    for zone in zones:
        if zone['name'] == DOMAIN:
            target_zone = zone
            break
else:
    response.raise_for_status()
    exit(1)

if not target_zone:
    print('Any zone found for domain %s' % (DOMAIN))
    exit(1)

domain_records_href = target_zone['zone_records_href']

# Get TXT record
response = requests.get(domain_records_href + "/" + ACME_RECORD, headers=headers)

# Delete record if it exists
if (response.ok):
    requests.delete(domain_records_href + "/" + ACME_RECORD, headers=headers)

if options.cleanup:
    exit(0)

print('Build DNS authentication')
record = {
    "rrset_name": ACME_RECORD,
    "rrset_type": "TXT",
    "rrset_ttl": 300,
    "rrset_values": [CERTBOT_VALIDATION],
    }

response = requests.post(domain_records_href,
                         headers=headers, json=record)

if (response.ok):
    print("DNS token created")
else:
    print("Something went wrong")
    response.raise_for_status()
    exit(1)

A downloadable version is available here

In /etc/crontab :

0  1   1 * *   root   certbot renew --manual-auth-hook /root/gandi_letsencrypt.py --manual-cleanup-hook /root/letsencrypt_token_cleanup.sh

Where /root/letsencrypt_token_cleanup.sh is

#!/bin/bash

/root/gandi_letsencrypt.py --cleanup

And in /etc/letsencrypt/renewal-hooks/post/ :

#!/bin/bash

service nginx restart
Dernier gif les joies du code Quand je compile mon code pour la première fois