NodeJS. Running as a service using forever on Ubuntu Server

После реализации nodejs приложения возникла задача его демонизации. Один из способов - использование модуля, разработанного nodejitsu - forever. Для установки данного модуля воспользуйтесь командой (Ubuntu Server):
 
sudo npm -g install forever

После этого можно запустить выполнение вашего nodejs приложения с помощью forever в виде самостоятельного сервиса:

forever start /path/to/directory/containing_your_app/your_app_name.js

forever будет отслеживать за падениями приложения и пытаться его поднять. В случае же рестарта системы, запуска вашего сервиса не произойдет. Здесь нужно реализовать автозагрузку на уровне системы. Создайте скрипт запуска в директории /etc/init.d. Ниже приведен пример скрипта автозапуска /etc/init.d/your_app_name:

#!/bin/bash
#
# description: your_app_name (node) service
#
# use commands:
# service your_app_name <command (start|stop|status|restart)>

SERVICE_NAME=your_app_name
SOURCE_DIR=/path/to/directory/containing_your_app
SOURCE_NAME=your_app_name.js
SOURCE_PATH=$SOURCE_DIR/$SOURCE_NAME
LOG_DIR=$SOURCE_DIR/forever
USER=tester

pidfile=/home/$USER/.forever/pids/$SERVICE_NAME.pid
logfile=$LOG_DIR/forever.log
outfile=$LOG_DIR/output.log
errfile=$LOG_DIR/error.log

case "$1" in
    start)
        forever start -l $logfile -o $outfile -e $errfile --pidFile $pidfile -a $SOURCE_PATH
        ;;
    stop)
        forever stop --pidFile $pidfile $SOURCE_PATH
        ;;
    status)
        forever list
        ;;
    restart)
        forever restart -l $logfile -o $outfile -e $errfile --pidFile $pidfile -a $SOURCE_PATH
        ;;
    *)
        echo "Usage: $SERVICE_NAME {start|stop|status|restart}"
        exit 1
        ;;
esac
exit 0

Далее выдайте права:

sudo chmod 755 /etc/init.d/your_app_name

и пропишите его в автозагрузку:

sudo update-rc.d your_app_name defaults

Oracle. ORA-21561: OID generation failed

После установки клиента при попытке коннекта выбрасывается следующая ошибка 

ORA-21561: OID generation failed

Для устранения необходимо добавить имя хоста в файл hosts:

$ hostname
$ nano /etc/hosts
 > 127.0.0.1      localhost      hostname

PLSQL. JSON validator (string validation)

Как известно, JSON формат представляет собой альтернативу XML формату. В настоящее время данный формат получил довольно широкое распространение. Так же и Oracle не отстает от времени и в версии 12.1.0.2 была добавлена нативная поддержка JSON формата. Если Вы работаете на более поздних версиях, то можно добавить собственные реализации для поддержки формата. Есть уже и готовые решения, например можно воспользоваться следующим ресурсом - https://github.com/pljson/pljson.

В моем же случае все немного проще. На стороне сервера по триггеру запускался обработчик, который формировал данные в строковом виде в JSON формате и после отправлял POST запросом на определенный ресурс. Перед отправкой необходимо было убедиться в валидности JSON формата. Так возникла потребность в организации такого рода JSON валидатора. За основу был взят JSON парсер, написанный Дугласом Крокфордом на javascript, с которым можно ознакомиться по ссылке - https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js. Данный парсер предоставляет альтернативу встроенному парсеру JSON.parse в случае отсутствия поддержки оного в Вашем браузере. Ниже представлена реализация тела пакета данного валидатора. В спецификации внешний доступ предоставлен трем методам:

function unsafety_validate(source in varchar2) return boolean;
function safety_validate(source in varchar2) return boolean;
function safety_validate(source in varchar2, errmsg in out varchar2) return boolean;

Префиксы - safety и unsafety - указывают на возможность генерирования возникающих в ходе валидации исключений. С полным описанием пакета можно ознакомиться по ссылке - https://github.com/mozg1984/PL-JSON-VALIDATOR.


create or replace package body json_validator is

  /* @private
   * type for instantiate keys storages
   */
  type array_t is table of varchar2(1000);

  atCh integer; -- the index of the current character
  ch char;      -- the current character
  text varchar2(32767);

  /* @private
   * call error when something is wrong
   */
  procedure error(m in varchar2 default '')
  is
    errmsg varchar2(32767);
  begin
    errmsg := 'name: SyntaxError, ';
    errmsg := errmsg || 'message: ' || m || ', ';
    errmsg := errmsg || 'at: ' || atCh || ', ';
    errmsg := errmsg || 'text: ' || text;
    raise_application_error(-20000, errmsg);
  end;

  /* @private
   * escape sequence
   */
  function escapee(ch in char default '') return varchar2
  is
  begin
    case ch
      when '"' then return '"';
      when '\' then return '\';
      when '/' then return '/';
      when 'b' then return '\b';
      when 'f' then return '\f';
      when 'n' then return '\n';
      when 'r' then return '\r';
      when 't' then return '\t';
      else return null;
    end case;
  end;

  /* @private
   * add key to keys storage
   */
  procedure add_key(p_array in out nocopy array_t, p_key in varchar2)
  is
  begin
    p_array.extend();
    p_array(p_array.count()) := p_key;
    exception
      when collection_is_null then 
        p_array := array_t(p_key);
  end;
  
  /* @private
   * check duplicate keys in keys storage
   */
  function has_key(p_array in out nocopy array_t, p_key in varchar2) return boolean
  is
    is_equal boolean := false;
  begin
    for i in p_array.first..p_array.last loop
      if p_array(i) = p_key then
        is_equal := true;
      end if; 
    end loop;
    
    return is_equal;
    exception
      when collection_is_null then 
        return is_equal;
  end;

  /* @private
   *  get char from string by index position
   */
  function charAt(str in varchar2, pos in integer) return char
  is
  begin
    return substr(str, pos, 1);
  end;

  /* @private
   * check if given number
   */
  function isNumber(num in varchar2) return boolean
  is
  begin
    case regexp_like(num, '^\d*\.?\d+$')
      when true then 
        return true;
      else 
        return false; 
    end case;
  end;
  
  /* @private
   * check if given character
   */
  function exist(ch in varchar2 default '') return boolean
  is
  begin
    return ch is not null; 
  end;

  /* @private
   * convert hex number (character) to decimal number
   */
  function hex2dec(ch in char) return number
  is
  begin
    return to_number(ch, 'x');
    exception
      when value_error then
        return null;
  end;
  
  /* @private
   * convert hex code to unicode character
   */
  function getUnicodeChar(hexcode in varchar2) return varchar2
  is
  begin
    return trim(unistr('\' || hexcode || ' '));
    exception
      when others then
        return ''; 
  end;

  /* @private
   * get the next character
   */
  function nextChar(c in char default '') return char
  is
  begin
    -- if a c parameter is provided, verify that it matches the current character
    if (exist(c) and c != ch) then
      error('Expected ''' || c || ''' instead of ''' || ch || '''');
    end if;

    -- when there are no more characters, return the empty string
    ch := charAt(text, atCh);
    atCh := atCh + 1;
    return ch;
  end;
  
  /* @private
   * move current char to next
   */
  procedure nextChar(c in char default '')
  is
  begin
    -- if a c parameter is provided, verify that it matches the current character
    if (exist(c) and c != ch) then
      error('Expected ''' || c || ''' instead of ''' || ch || '''');
    end if;

    ch := charAt(text, atCh);
    atCh := atCh + 1;
  end;

  /* @private
   * forward declaration of common validate function 
   */
  function validate return boolean;

  /* @private
   * validate a number value
   */
  function validateNumber return boolean
  is
    l_number varchar2(32767) := '';
  begin
    if (ch = '-') then
      l_number := '-';
      nextChar('-');
    end if;

    while (ch >= '0' and ch <= '9') loop
      l_number := l_number || ch;
      nextChar();
    end loop;

    if (ch = '.') then
      l_number := l_number || '.';
      while (exist(nextChar()) and ch >= '0' and ch <= '9') loop
        l_number := l_number || ch;
      end loop;
    end if;

    if (ch = 'e' or ch = 'E') then
      l_number := l_number || ch;
      nextChar();

      if (ch = '-' or ch = '+') then
        l_number := l_number || ch;
        nextChar();
      end if;

      while (ch >= '0' and ch <= '9') loop
        l_number := l_number || ch;
        nextChar();
      end loop;
    end if;

    if (not(isNumber(l_number))) then
      error('Bad number');
    else
      return true;
    end if;
  end;

  /* @private
   * validate a string value
   */
  function validateString return boolean
  is
    decnum number;
  begin
    -- when parsing for string values, we must look for " and \ characters
    if (ch = '"') then
      while (exist(nextChar())) loop
        if (ch = '"') then
          nextChar();
          return true;
        end if;

        if (ch = '\') then
          nextChar();
          if (ch = 'u') then
            for i in 1..4 loop
              decnum := hex2dec(nextChar());              
              if (not isNumber(decnum)) then
                error('Malformed Unicode character escape sequence');
              end if;
            end loop;
          elsif (escapee(ch) is not null) then
            null;
          else
            exit;
          end if;
        end if;
      end loop;
    end if;
    error('Bad string');
  end;
  
  /* @private
   * get string key value (for checking duplicate keys in object value)
   */
  function stringKey return varchar2
  is
    l_string varchar2(32767) := '';
    decnum number;
    uffff varchar2(32767);
  begin
    -- when parsing for string values, we must look for " and \ characters
    if (ch = '"') then
      while (exist(nextChar())) loop
        if (ch = '"') then
          nextChar();
          return l_string;
        end if;

        if (ch = '\') then
          nextChar();
          if (ch = 'u') then
            uffff := '';
            for i in 1..4 loop
              decnum := hex2dec(nextChar());
              if (not isNumber(decnum)) then
                error('Malformed Unicode character escape sequence');
              end if;
              uffff := uffff || ch;
            end loop;
            l_string := l_string || getUnicodeChar(uffff);
          elsif (escapee(ch) is not null) then
            l_string := l_string || escapee(ch);
          else
            exit;
          end if;
        else
          l_string := l_string || ch;
        end if;
      end loop;
    end if;
    error('Bad string');
  end;
  
  /* @private
   * skip whitespace
   */
  procedure white 
  is
  begin
    while (exist(ch) and ch <= ' ') loop
      nextChar();
    end loop;
  end;

  /* @private
   * validate a word (true, false or null)
   */
  function validateWord return boolean
  is
  begin
    case ch
      when 't' then
        nextChar('t');
        nextChar('r');
        nextChar('u');
        nextChar('e');
        return true;
      when 'f' then
        nextChar('f');
        nextChar('a');
        nextChar('l');
        nextChar('s');
        nextChar('e');
        return true;
      when 'n' then
        nextChar('n');
        nextChar('u');
        nextChar('l');
        nextChar('l');
        return true;
      else
        error('Unexpected ''' || ch || '''');
    end case; 
  end;
  
  /* @private
   * validate an array value
   */
  function validateArray return boolean
  is
    l_result boolean := true;
  begin
    if (ch = '[') then
      nextChar('[');
      white();
      if (ch = ']') then
        nextChar(']');
        return l_result; -- empty array
      end if;
      
      while (exist(ch)) loop
        l_result := validate();
        white();
        if (ch = ']') then
          nextChar(']');
          return l_result;
        end if;
        nextChar(',');
        white();
      end loop;
    end if;
    error('Bad array');
  end;

  /* @private
   * validate an object value
   */
  function validateObject return boolean
  is
    l_result boolean := true;
    l_key varchar2(1000);
    l_keys array_t;
  begin
    if (ch = '{') then
      nextChar('{');
      white();
      if (ch = '}') then
        nextChar('}');
        return l_result; -- empty object
      end if;
      
      while (exist(ch)) loop
        l_key := stringKey();
        white();
        nextChar(':');
        
        if (has_key(l_keys, l_key)) then
          error('Duplicate key "' || l_key || '"');
        end if;
        
        add_key(l_keys, l_key);
        l_result := validate();
        white();
        
        if (ch = '}') then
          nextChar('}');
          return l_result;
        end if;
        
        nextChar(',');
        white();
      end loop;
    end if;
    error('Bad object');
  end;

  /* @public
   * validate a JSON value. 
   * It could be an object, an array, a string, a number, or a word.
   */
  function validate return boolean
  is
  begin
    white();
    case ch
      when '{' then
        return validateObject();
      when '[' then
        return validateArray();
      when '"' then
        return validateString();
      when '-' then
        return validateNumber();
      else
        return case
          when (ch >= '0' and ch <= '9') then validateNumber() else validateWord() end; 
    end case;
  end;
  
  /* @public
   * unsafety validate JSON string (throw exception ora-20000)
   */
  function unsafety_validate(source in varchar2) return boolean
  is
    l_result boolean;
  begin
    text := source;
    atCh := 1;
    ch := ' ';
    
    l_result := validate();
    white();
    
    if (exist(ch)) then
      error('Syntax error');
    end if;
    
    return l_result; 
  end;
  
  /* @public
   * safety validate JSON string (catch all exceptions)
   */
  function safety_validate(source in varchar2) return boolean
  is
    l_result boolean;
  begin
    text := source;
    atCh := 1;
    ch := ' ';
    
    l_result := validate();
    white();
    
    if (exist(ch)) then
      error('Syntax error');
    end if;
    
    return l_result;
    
    exception
      when others then
        return false;
  end;
  
  /* @public
   * safety validate JSON string (catch all exceptions with error message) 
   */
  function safety_validate(source in varchar2,
                           errmsg in out varchar2) return boolean
  is
    l_result boolean;
  begin
    text := source;
    atCh := 1;
    ch := ' ';
    
    l_result := validate();
    white();
    
    if (exist(ch)) then
      error('Syntax error');
    end if;
    
    return l_result;
    
    exception
      when others then
        errmsg := sqlerrm;
        return false;
  end;

begin
  -- init
  null;
end json_validator;


Пример использования данного пакета


declare
  json_string varchar2(1000);
  errmsg varchar2(32767);
begin
  
  json_string := '{"number": 123456, "text": "active", "array": [], "object": {}}';
  
  if json_validator.safety_validate(json_string, errmsg) then
    dbms_output.put_line('JSON is valid');
  else
    dbms_output.put_line('JSON is not valid');
    dbms_output.put_line(errmsg);
  end if;
  
end;

Javascript. Unicode char by hex code

Ниже представлена реализация получения unicode-символа по шестнадцатеричному коду в методе getUnicodeChar утилитного объекта util.

// utility object
var util = {   
    // modulo
    mod: function (a, b) {
        return a % b;
    },

    // integer division
    div: function (a, b) {
        return (a - a % b) / b;
    },

    // convert hex to decimal number
    hex2dec: function (hex) {
        hex = '' + hex;
       
        var result = 0,
              dec, i, len;
       
        for (i = 0, len = hex.length; i < len; i++) {
            dec = parseInt(hex.charAt(i), 16);
            if (!isFinite(dec)) {
                break;
            }
            result = result * 16 + dec;
        }
       
        return result;
    },

    // convert decimal number to hex
    dec2hex: function (number) {
        number = +number;
       
        var dec = 0,
              result = '',
              self = this;
       
        while ((dec = self.mod(number, 16)) > 0) {
            result = dec.toString(16) + result;
            number = self.div(number, 16);
        }
       
        return result;
    },
   
    // get unicode char
    getUnicodeChar: function (unicode) {
        var unicodeChar = '',
              uffff = 0,
              self = this;
       
        if (unicode.charAt(0) === '\\' && unicode.charAt(1) === 'u') {
            uffff = self.hex2dec(unicode.substr(2));
        }
       
        return unicodeChar + String.fromCharCode(uffff);
    }
};


console.log(util.getUnicodeChar('\\u041a')); // К
console.log(util.hex2dec('041a')); // 1050
console.log(util.dec2hex(1050));  // 41a


Стоит отметить, что методы hex2dec и dec2hex можно реализовать и более простым способом.

// convert hex to decimal number
function hex2dec(hex) {
    return parseInt(hex, 16);
}

// convert decimal number to hex
function dec2hex(dec) {
    return dec.toString(16);
}


console.log(hex2dec('041a')); // 1050
console.log(dec2hex(1050));   // 41a

PLSQL. Object type reflection

Возможно ли просматривать начинку объектных типов независимо от их типа. Для этого в Oracle есть поддержка таких универсальных типов как ANYTYPE, ANYDATA и ANYDATASET. Ниже представлен пример рефлексии объектных типов, с элементами типа VARCHAR2, NUMBER и DATE, при желании список поддерживаемых типов можно расширить (см. документацию).

-- sql
create or replace type order_t as object (
   order_id number,
   order_date date,
   description varchar2(1000)
)

-- pl/sql
declare
  v_order order_t := order_t(123456, sysdate, 'Description text');
  v_anydata anydata;

  -- print object information (reflection)
  procedure objectInfo(p_object in out anydata) -- out for piecewise
  is
    l_typecode pls_integer;
    l_anytype anytype;
    l_result pls_integer;
    l_varchar2 varchar2(32767);
    l_number number;
    l_date date;
    
    -- type-level metadata
    type typeinfo_r is record ( 
      prec        pls_integer
     ,scale       pls_integer
     ,len         pls_integer
     ,csid        pls_integer
     ,csfrm       pls_integer
     ,schema_name varchar2(30)
     ,type_name   varchar2(30)
     ,version     varchar2(30)
     ,count       pls_integer
    );
    typeinfo typeinfo_r;

    -- attribute-level metadata
    type attrinfo_r is record (
      prec           pls_integer
     ,scale          pls_integer
     ,len            pls_integer
     ,csid           pls_integer
     ,csfrm          pls_integer
     ,attr_elt_type  anytype
     ,aname          varchar2(32767)
    );
    attrinfo attrinfo_r;
      
  begin
    -- get information for the ANYDATA instance
    l_typecode := p_object.getType(l_anytype);
    dbms_output.put_line('Typecode: ' || l_typecode);

    if l_typecode = dbms_types.typecode_object then -- if given object
      p_object.piecewise();
      
      -- get type-level metadata
      l_typecode := l_anytype.GetInfo(typeinfo.prec,
                                      typeinfo.scale,
                                      typeinfo.len,
                                      typeinfo.csid,
                                      typeinfo.csfrm,
                                      typeinfo.schema_name,
                                      typeinfo.type_name,
                                      typeinfo.version,
                                      typeinfo.count);
      
      dbms_output.put_line('-------------------------------------');
      dbms_output.put_line('Typename: ' || typeinfo.type_name);
      dbms_output.put_line('Attributes: ' || typeinfo.count);
      
      for i in 1..typeinfo.count loop -- loop by atrributes 
        -- get attribute-level metadata
        l_typecode := l_anytype.getAttrElemInfo(i,
                                                attrinfo.prec,
                                                attrinfo.scale,
                                                attrinfo.len,
                                                attrinfo.csid,
                                                attrinfo.csfrm,
                                                attrinfo.attr_elt_type,
                                                attrinfo.aname);
        
        dbms_output.put_line('-------------------------------------');
        dbms_output.put_line('Attribute: ' || attrinfo.aname);
        dbms_output.put_line('Typecode: ' || l_typecode);
        dbms_output.put_line('Length: ' || nvl(attrinfo.len, attrinfo.prec));
           
        if l_typecode = dbms_types.typecode_varchar2 then -- varchar2
          l_result := p_object.getVarchar2(c => l_varchar2);
          dbms_output.put_line('Value: ' || l_varchar2);
          
        elsif l_typecode = dbms_types.typecode_number then -- number
          l_result := p_object.getNumber(num => l_number);
          dbms_output.put_line('Value: ' || l_number);
          
        elsif l_typecode = dbms_types.typecode_date then -- date
          l_result := p_object.getDate(dat => l_date);
          dbms_output.put_line('Value: ' || to_char(l_date, 'dd.mm.yyyy'));
          
        else
          raise_application_error(-20000, 'Unexpected ' || i || 'st attribute typecode: ' || l_typecode);
        end if;
          
      end loop;
    end if;      
  end;

begin
  
  v_anydata := anydata.convertObject(v_order);
  objectInfo(v_anydata);

end;



Typecode: 108
-------------------------------------
Typename: ORDER_T
Attributes: 3
-------------------------------------
Attribute: ORDER_ID
Typecode: 2
Length: 0
Value: 123456
-------------------------------------
Attribute: ORDER_DATE
Typecode: 12
Length: 
Value: 15.10.2015
-------------------------------------
Attribute: DESCRIPTION
Typecode: 9
Length: 1000
Value: Description text

oracle v.11.2. Escape and unescape url in cyrillic utf-8

Всем знакомы проблемы с кодировками на кириллице, на Oracle есть замечательный пакет utl_url, который позволяет кодировать символы в utf8 формате. Данный пакет может особо пригодиться, при использовании пакета utl_http во взаимодействии с веб серверами. Попробуйте наладить отправку данных без поддержки этих методов и Вы поймете о чем идет речь. Ниже приведены примеры их использования.

declare
  url varchar2(100) := 'http://example.com/?language=русский';
begin
  url := utl_url.escape(url, true, 'utf-8');
  dbms_output.put_line(url);
  
  url := utl_url.unescape(url, 'utf-8');
  dbms_output.put_line(url);
end;

На выходе:

http%3A%2F%2Fexample.com%2F%3Flanguage%3D%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9

http://example.com/?language=русский

oracle v.11.2. Get md5 hash

select 
  lower(dbms_obfuscation_toolkit.md5(input => utl_raw.cast_to_raw('qwerty'))) md5hash
from dual;

d8578edf8458ce06fbc5bb76a58c5ca4

C. Support private objects in functions

Можно ли добиться поддержки приватности в структурном программировании на C? Javascript может поддерживать приватность за счет техники замыкания, C, конечно, такой возможности не имеет, но можно воспользоваться статическими объектами (с точки зрения класса памяти). Ниже приведен один из подходов

#include <stdio.h>

// init counter action
void init(int *cnt) {
  *cnt = 0;
}

// increment counter action
void increment(int *cnt) {
  (*cnt)++; 
}

// decrement counter action
void decrement(int *cnt) {
  (*cnt)--;
}

// show counter action
void show(const int *cnt) {
  printf("counter = %d\n", *cnt);
}

// counter encapsulation
void counter(void (*action) (int*)) {
  static int cnt = 0; // private
  action(&cnt);
}

int main() {
  
  counter(init);            // counter = 0;
  counter(show);         // counter = 0;
  counter(increment); // counter = 1;
  counter(increment); // counter = 2;
  counter(show);         // counter = 2;

  return 0;
}

В данном случае функции определяют возможные действия над приватным счетчиком, другие действия, как и прямое обращение к значению счетчика (статическая переменная cnt), недопустимы. Можно усложнить данную реализацию для повышения функционала действий над приватным счетчиком. Вот одна из реализаций

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

// check requested init action
int is_init(const char *action) {
  return !strcmp(action, "init");
}

// check requested increment action 
int is_increment(const char *action) {
  return !strcmp(action, "increment");
}

// check requested decrement action
int is_decrement(const char *action) {
  return !strcmp(action, "decrement");


// check requested show action 
int is_show(const char *action) {
  return !strcmp(action, "show");
}

// init counter action
void init(int *cnt) {
  *cnt = 0;
}

// increment counter action
void increment(int *cnt, int n) {
  (*cnt) += n;
}

// decrement counter action
void decrement(int *cnt, int n) {
  (*cnt) -= n;
}

// show counter action
void show(const int *cnt) {
  printf("counter = %d\n", *cnt);
}

// counter encapsulation
void counter(char *action, ...) {
  static int cnt = 0;// private

  va_list par;
  va_start(par, action);
  int n = *((int *) par);// get argument value
  *((int *) par) = 0;// clear argument value
  va_end(par);

  n = n > 1 ? n : 1;

  #ifdef DEBUGMODE
printf("debug: action = %s, counter = %d, n = %d\n", action, cnt, n);
  #endif

  if (is_init(action))
    init(&cnt);
  else if (is_increment(action))
    increment(&cnt, n); 
  else if (is_decrement(action))
    decrement(&cnt, n);
  else if (is_show(action))
    show(&cnt);
  else
    printf("unsupported action\n");
}

int main() {

  counter("init");                 // counter = 0
  counter("increment");    // counter = 1
  counter("increment");    // counter = 2
  counter("show");              // counter = 2
  counter("increment", 5); // counter = 7
  counter("show");               // counter = 7
  counter("decrement");     // counter = 6
  counter("show");               // counter = 6
  counter("decrement", 3); // counter = 3
  counter("show");               // counter = 3
  counter("init");                  // counter = 0
  counter("show");               // counter = 0
  counter("test");                  // unsupported action

  return 0;
}

Или так

#include <stdio.h>
#include <stdarg.h>

// actions
typedef enum {init, increment, decrement, show, test} actions;

// check requested init action
int is_init(actions action) {
  return action == init;
}

// check requested increment action 
int is_increment(actions action) {
  return action == increment;
}

// check requested decrement action
int is_decrement(actions action) {
  return action == decrement;


// check requested show action 
int is_show(actions action) {
  return action == show;
}

// init counter action
void init_action(int *cnt) {
  *cnt = 0;
}

// increment counter action
void increment_action(int *cnt, int n) {
  (*cnt) += n;
}

// decrement counter action
void decrement_action(int *cnt, int n) {
  (*cnt) -= n;
}

// show counter action
void show_action(const int *cnt) {
  printf("counter = %d\n", *cnt);
}

// counter encapsulation
void counter(actions action, ...) {
  static int cnt = 0; // private

  va_list par;
  va_start(par, action);
  int n = *((int *) par);// get argument value
  *((int *) par) = 0;// clear argument value
  va_end(par);

  n = n > 1 ? n : 1;

  #ifdef DEBUGMODE
  printf("debug: action = %s, counter = %d, n = %d\n", action, cnt, n);
  #endif

  if (is_init(action))
    init_action(&cnt);
  else if (is_increment(action))
    increment_action(&cnt, n); 
  else if (is_decrement(action))
    decrement_action(&cnt, n);
  else if (is_show(action))
    show_action(&cnt);
  else
    printf("unsupported action\n");
}

int main() {

  counter(init);                  // counter = 0
  counter(increment);     // counter = 1
  counter(increment);     // counter = 2
  counter(show);               // counter = 2
  counter(increment, 5); // counter = 7
  counter(show);               // counter = 7
  counter(decrement);     // counter = 6
  counter(show);               // counter = 6
  counter(decrement, 3); // counter = 3
  counter(show);                // counter = 3
  counter(init);                   // counter = 0
  counter(show);                // counter = 0
  counter(test);                   // unsupported action

  return 0;
}