Archivo

Archivo para la categoría ‘C/C++’

[Arduino] Utilizando la memoria Flash en lugar de la SRAM para constantes

Viernes, 2 de Diciembre de 2011 Gaspar Fernández Sin comentarios

temp_ardublogOtra cosa no, pero los Arduino no son conocidos por su gran memoria RAM, y es que, por ejemplo en la serie Diecimila, con el Atmega168 tenemos 1Kb de RAM, con el Atmega328, hay 2Kb de RAM, aunque puede que para algunos de nuestros programas nos quedemos un poco cortos.

Una gran ayuda para esto puede ser utilizar las constantes que cree nuestro programa, en forma numérica de tabla de valores constante, o de cadena de caracteres, por ejemplo, para enviar mensajes predeterminados por el Serial, decir el nombre de la aplicación, la versión, etc.

PROGMEM

Será una macro creada para almacenar datos en espacio de programa. El programa no ocupará más, y tendremos más memoria libre para utilizar y reservar a nuestro antojo.

Antes de utilizar PROGMEM, debemos hacer

1
#include <avr/pgmspace.h>

y así poder tener acceso a todas las funciones adicionales que nos proporciona esta biblioteca.

Viendo la memoria libre

Hay una función que encontré aquí, un poco chapucera, pero eficiente (en la web hay mejores funciones, pero esta es la primera que encontramos), y que calcula el espacio que queda en la memoria (en bytes):

1
2
3
4
5
6
7
8
9
10
11
12
13
// this function will return the number of bytes currently free in RAM
// written by David A. Mellis
// based on code by Rob Faludi http://www.faludi.com
int availableMemory() {
  int size = 1024; // Use 2048 with ATmega328
  byte *buf;

  while ((buf = (byte *) malloc(--size)) == NULL);

  free(buf);

  return size;
}

Con esta función podemos ver la memoria que nos queda:

PROGMEM CON NÚMEROS (int, float, char, byte, unsingeds…)

Para probarlo, lo mejor es ver una demostración (no he incluido la función availableMemory(), copiad y pegad de arriba):

1
2
3
4
5
6
7
8
9
10
11
12
void setup()
{
  Serial.begin(9600);
}

PROGMEM int numero=25;
void loop()
{
  Serial.println(numero, DEC);
  Serial.println(availableMemory(), DEC);
  delay(1000);
}

Podemos ver cómo numero está declarado como PROGMEM int, bien, eliminemos el PROGMEM y vemos qué hace, ¡tenemos 2 bytes menos libres! Aquí demostramos que de verdad no estamos utilizando la RAM.

Arrays de números

Ahora viene lo bueno, no hacemos nada si sólo almacenamos en Flash valores, por separado, lo interesante es poder almacenar arrays con lo que tendremos muchas más posibilidades.
Por ejemplo, podemos hacer:

1
2
3
4
5
6
7
8
9
10
11
12
PROGMEM int numeros[]={10, 29, 38, 47, 56, 64, 73, 82, 91, 0};

void loop()
{
  static int pos=0;
  Serial.println(pgm_read_word(&numeros[pos++]));
  Serial.println(availableMemory(), DEC);
  if (pos==10)
    pos=0;

  delay(1000);
}

Cada segundo mostrará por pantalla un número del array de enteros, y todos estarán en Flash, el coste de eso será de unos 100bytes más en el binario que, por tanto también irá a Flash, además de algunos ciclos de procesador; aunque en este caso, importa más la memoria.

He utilizado pgm_read_word() porque el array es de enteros (2 bytes = 1 word), si nuestra variable fuera de 1 byte (char, byte) se podrá utilizar pgm_read_byte() y si la variable es de 4 bytes (long) podremos utilizar pgm_read_dword(), para variables tipo float tenemos de igual manera pgm_read_float().

Cadenas de caracteres sin buffer

Para escribir cadenas de caracteres, lo mejor es utilizar un buffer (pero ya estamos gastando memoria), por tanto vamos a hacer un ejemplo para imprimir por el Serial sin necesidad de utilizar buffer:

1
2
3
4
5
6
7
8
9
10
11
12
13
char mens[] PROGMEM = "Hola mundo cruel y despiadado";

void loop()
{
  char *mem=mens;

  while (pgm_read_byte(mem) != 0x00) /* Comparamos con \0, un terminador */
    Serial.print(pgm_read_byte(mem++));
  Serial.println();

  Serial.println(availableMemory());
  delay(1000);
}

Podemos hacer el mensaje más largo, que seguimos consumiendo la misma cantidad de memorial. Para imprimir por el Serial con esta técnica podemos crear una función (printpgm()):

1
2
3
4
5
6
7
8
void printpgm(char *texto)
{
  char *mem=texto;

  while (pgm_read_byte(mem) != 0x00) /* Comparamos con \0, un terminador */
    Serial.print(pgm_read_byte(mem++));
  Serial.println();
}

o como comentan aquí, modificar la clase HardwareSerial para incluir un método que imprima cadenas de caracteres procedentes de la memoria de programa.
Y creando estas funciones nos llevamos alguna que otra sorpresa en memoria libre (aunque pequeña).

Listas enlazadas en Arduino (primera versión)

Miércoles, 30 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Es parte de un proyecto personal que quiero compartir. Se trata de una clase para utilizar listas enlazadas desde Arduino, aunque la memoria RAM de estos bichillos no sea gran cosa (1K para la versión Diecimila), puede dar para mucho, y es que hay ocasiones en las que podemos necesitar esta flexibilidad a la hora de almacenar y acceder a la información (aunque no pueda ser mucha).

La clase ha sido desarrollada utilizando templates, por lo que podréis particularizar esta clase para cualquier tipo de dato que estéis manejando. Declarando la lista de la siguiente forma:

1
LList <tipo> lista;

Antes de dar el código, hagamos algunas operaciones, vamos a ver lo que podemos hacer con 1K (vale con 600 bytes libres):

  • Con un tipo int, cada nodo ocupará 4 bytes: 600 / 4 = 150 nodos (alguno menos en realidad). Pero estos nodos pueden indicar posiciones en un mapa, un pequeño informe de lo que ha transcurrido, lecturas, estado de periféricos, etc
  • Con un tipo:
    1
    2
    3
    4
    5
    struct Map
    {
      int key;
      int value;
    }

    : podemos almacenar 600/6 = 100 elementos con clave y valor, por ejemplo, una matriz o vector disperso que nos pueden ayudar para un cálculo matemático

  • Lo malo son las cadenas, no podemos escribir un libro, pero si hacemos un tipo que almacene unas 28 letras, cuyo nodo ocupará 30 bytes, podremos almacenar 600/30=20 mensajes distintos, aunque recomiendo que esos mensajes sean generados por el programa, ya que si los mensajes son predefinidos, podemos almacenarlos en la memoria flash, que tendremos más sitio.

Ahí va el código para los copy-paste aunque el archivo lo podéis bajar de aquí (9.5Kb). Por cierto, al final del archivo hay un código de ejemplo para probar que todo funcione bien.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/**
*************************************************************
* @file ArduinoList.pde
* Another implementation of linked lists to use in Arduino.
*    Copyright (C) 2011 Gaspar Fernández
*
*    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/>.
*
* @brief Linked list implementation for Arduino
*
* Features:
*   - Can access to head and tail directly
*   - Can pop back and front from this class
*   - Can access any item from the item number
*   - Can set a callback function when an error occurs
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @web http://totaki.com/poesiabinaria
* @version 0.1
* @date 27 nov 2011
* Change history
*
* To-do:
*   - Better control over non-critical errors, maybe another callback
*   - Pre-defined error methods: by serial, by output leds...
*************************************************************/


#define LLIST_ERROR_NOMEMORY     1 // Not enough memory to insert element
#define LLIST_ERROR_NOTAIL       2 // Can't get last items
#define LLIST_ERROR_NOFRONT      3 // Can't get head items
#define LLIST_ERROR_NOITEMS      4 // List empty, can't read items
#define LLIST_ERROR_NEITEMS      5 // Not enough items (pos>=sz)

template <typename Type>
class LList
{
public:
  typedef void (*ErrorCallback)(int);

  // We can add more error types
  enum ListErrorType
    {
      NO_ERROR_REPORTING,   // No error reporting
      CALLBACK          // Errors will trigger a callback
    };

  // Destruction. Clear data
  ~LList();

  // Initialization
  LList();

  // Inserts new item in the last position
  void push_back(const Type item);
  // Is the list empty?
  inline bool isEmpty() const;
  // Returns the size of the list
  inline int size();
  // Returns the last item
  inline Type back();
  // Returns the first item
  inline Type front();
  // Return the last item and delete it
  Type pop_back();
  // Return the first item and delete it
  Type pop_front();

  // Get the item at position pos
  Type getElement(unsigned pos);
  // Insert a new item at position pos
  void insert(int pos, Type item);
  // set error callback function
  void setErrorCallback(ErrorCallback cback);
  // no error reporting
  void setNoErrorReporting();
  // clears the list
  void clear();
private:
  // Reports an error
  void report_error(int err);
 
  // Deletes a list when there is only one element
  void delete_list();

  typedef struct node
  {
    Type item;
    node *next;
  } node;

  node *head;           // Pointer to the head
  node *tail;           // Pointer to the tail
  int sz;           // List size
  ListErrorType errorType; 
  ErrorCallback ecback;     // Callback when reporting an error
};

template <typename Type>
LList<Type>::LList()
{
  head=NULL;
  tail=NULL;
  sz=0;
  errorType=NO_ERROR_REPORTING;
}

template <typename Type>
LList<Type>::~LList()
{
  clear();          // Clears the list before destroying
}

template <typename Type>
void LList<Type>::push_back(const Type item)
{
  node *aux=tail;
 
  // Reserve memory for a new node
  tail=(node*)malloc(sizeof(node));
  if (tail==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }

  // Stores the item information
  tail->item=item;
  tail->next=NULL;

  // Link to the list
  if (isEmpty())
    head=tail;
  else
    aux->next=tail;

  sz++;

}

template <typename Type>
bool LList<Type>::isEmpty() const
{
  return (sz==0);       // (sz==0) = EMPTY
}

template <typename Type>
int LList<Type>::size()
{
  return sz;
}

template <typename Type>
inline Type LList<Type>::back()
{
  if (isEmpty())        // List empty = No last item
    {
      Type t;
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }

  return tail->item;
}

template <typename Type>
inline Type LList<Type>::front()
{
  if (isEmpty())        // List empty = No first item
    {
      Type t;
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  return head->item;
}

template <typename Type>
Type LList<Type>::pop_back()
{
  node *aux=head;
  Type t;

  if (isEmpty())        // List empty = No last item
    {
      report_error(LLIST_ERROR_NOTAIL);
      return t;
    }
 
  if (head==tail)       // If there is only 1 item
    {
      t=head->item;
      delete_list();
      return t;
    }
  else
    {               // More than one item
      while (aux->next!=tail)   // Searches for the item previous to the last
    aux=aux->next;
     
      t=tail->item;     // I will return the tail item
      free(tail);      
      tail=aux;         // Define the new tail
      tail->next=NULL;
      sz--;         // 1 item less

      return t;
    }
}

template <typename Type>
Type LList<Type>::pop_front()
{
  Type t;
  node *aux=head;
 
  if (isEmpty())        // List empty = No head
    {
      report_error(LLIST_ERROR_NOFRONT);
      return t;
    }

  t=aux->item;          // I will return the head
  head=head->next;      // The new head is the item after the head
  free(aux);
  if (head==NULL)       // If I had deleted the last item, replace the tail
    tail=NULL;          // too.
  sz--;

  return t;
}

template <typename Type>
void LList<Type>::delete_list()
{
  free(head);           // I will call this method when deleting
  head=NULL;            // a list with only one element, so all
  tail=NULL;            // the variables are set.
  sz=0;
}

template <typename Type>
void LList<Type>::insert(int pos, Type item)
{
  node *newitem;
  node *aux=head;
  int i;

  // Allocate memory for the new item
  newitem=(node*)malloc(sizeof(node));
  if (newitem==NULL)
    {
      report_error(LLIST_ERROR_NOMEMORY);
      return;
    }
  // Stores the item information
  newitem->item=item;
  newitem->next=NULL;

  // Link the item to the list
  if (isEmpty())
    {               // If the list is empty there will be only
      head=newitem;     // one item, so it will be the head and the
      tail=newitem;     // tail.
    }
  else if (pos==0)      // If the item is going to be inserted at the beginning
    {
      newitem->next=head;   // we will move the head
      head=newitem;
    }
  else if (pos>=sz)     // If the position is greater than the last item's position
    {               // it will be inserted after this item, at the tail.
      tail->next=newitem;      
      tail=newitem;
    }
  else
    {               // If not, we will have to find the position of the
      i=0;          // previous item to link its next pointer to the new
      while (i<pos-1)       // element.
    {
      aux=aux->next;
      ++i;
    }
      newitem->next=aux->next;  // And then link the next pointer of our new element
      aux->next=newitem;    // to where the next pointer of the previour element
    }               // was pointing.
  sz++;
}

template <typename Type>
Type LList<Type>::getElement(unsigned pos)
{
  int i=0;
  Type t;
  node *aux=head;

  if (isEmpty())        // If the list is empty = No data
    {
      report_error(LLIST_ERROR_NOITEMS);
      return t;
    }

  if (pos>=sz)          // If the item asked for is greater than the
    {               // last item's position. There is no valid element
      report_error(LLIST_ERROR_NEITEMS);
      return t;
    }
 
  if (pos==sz-1)        // If the item we asked for is the last, we can
    return tail->item;      // return it inmediately


  while (i<pos)         // If not, we must look for it
    {
      aux=aux->next;
      i++;
    }
  return aux->item;
}

template <typename Type>
void LList<Type>::setErrorCallback(ErrorCallback cback)
{
  ecback=cback;         // This method sets the error callback to invoke
  errorType=CALLBACK;       // when an error occurs.
}

template <typename Type>
void LList<Type>::report_error(int err)
{
  if (errorType==CALLBACK)  // If the error reporting is through a callback,
    {               // call it.
      ecback(err);
    }
}

template <typename Type>
void LList<Type>::clear()
{
  node *aux;

  while (head!=NULL)        // Delete all elements
    {
      aux=head;
      head=head->next;
      free(aux);
    }
 
  tail=NULL;            // Restore variable stats
  sz=0;
}  

template <typename Type>
void LList<Type>::setNoErrorReporting()
{
  errorType=NO_ERROR_REPORTING;
}

// Test program
// LList<int> l;

// void blink(int err)
// {
//   while (1)
//     {
//       digitalWrite(11,HIGH);
//       delay(1000);
//       digitalWrite(11, LOW);
//       delay(1000);  
//     }
// }

// void setup()
// {
//   pinMode(11, OUTPUT);
//   Serial.begin(9600);
//   l.setErrorCallback(blink);
//   l.push_back(45);
//   l.push_back(42);
//   l.push_back(47);
//   l.push_back(89);
//   l.insert(0, 33);
//   l.insert(1, 53);
//   l.insert(9, 73);
//   // 33
//   // 53
//   // 45
//   // 42
//   // 47
//   // 89
//   // 73
// }

// void loop()
// {
//   char r;
//   if (Serial.available()>0)
//     {
//       while (Serial.available()>0)
//  {
//    r=Serial.read();
//    delayMicroseconds(10000);
//  }

//       for (unsigned i=0; i<l.size(); i++)
//  {
//    Serial.println(l.getElement(i), DEC);
//  }
//       Serial.print("Elements: ");
//       Serial.println(l.size(), DEC);
//       Serial.print("Last: ");
//       Serial.println(l.pop_front(), DEC);
//     }
// }

Iniciación a los memory leaks [ejemplos en C++]

Lunes, 28 de Noviembre de 2011 Gaspar Fernández Sin comentarios

leak

Hablemos de un fenómeno que nos afecta, sobre todo en desarrollos que están en ejecución durante mucho tiempo, pero , y es que, debido a la mala gestión de la memoria podemos llegar a consumir más de lo necesario y podemos volver loco al sistema operativo utilizando la memoria virtual para darnos el espacio que necesitamos.

Los memory leaks son fugas de memoria debidas a que hemos pedido un cierto espacio de memoria durante la ejecución de nuestro programa, la hemos usado y cuando hemos dejado de usarla, no la hemos liberado, por tanto estamos ocupando más de lo que necesitamos y acaparando recursos; un ejemplo de esto son esos procesos o programas que tenemos arrancados durante varios días, y cuanto más tiempo llevan arrancados más memoria ocupan (a veces es necesario, pero otras veces, la mayoría, no).

Antes de iniciarnos en la creación de un programa acerca de lo que no debemos hacer, vamos a hacerlo bien, y echaremos un vistazo a un artículo sobre el Out Of Memory Killer (desconozco si Windows tiene algo parecido, tal vez instalando alguna aplicación), que será una utilidad de nuestro kernel que matará el proceso cuando esté consumiendo mucha memoria, o nos permitirá matarlo fácilmente con las teclas (Alt+SysRq o imprimir pantalla+F). Con unas líneas antes de nuestro programa, nos podemos evitar un disgusto, como puede ser un reinicio de sistema mientras hacemos pruebas, y puede que estemos ejecutando algo útil.

¡Precaución!

Si vemos que nuestro proceso hace que el sistema vaya muy lento, será porque en lugar de utilizar nuestra RAM, estará usando la memoria virtual de forma intensa, esta memoria es mucho más lenta que nuestra RAM, si hemos entendido lo que quiero demostrar, podemos parar el proceso gracias al OOM Killer de antes con Alt+SysRq+F, pero sólo pulsa una vez las teclas, aunque veamos que no nos hace mucho caso, el sistema tarda un tiempo en volver a recolocar toda la información y limpiar la memoria del proceso. Si pulsamos más veces, lo mismo terminamos matando el servidor gráfico o algún proceso importante, y no queremos eso.

Ajustando OOM Killer

Para todo eso, debemos incluir el siguiente código (al final pondré todo el programa, pero primero lo analizamos por separado), el objetivo es escribir el valor 15 sobre el fichero /proc/PID/oom_adj:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void error(const char *mens)
{
  cerr << "ERROR: ("<<mens<<") errno: "<<errno<<": "<<strerror(errno)<<endl;
  exit (1);
}

void ajusta_oom()
{
  ofstream fi;
  char nom[30];

  // Obtenemos el nombre del fichero, aunque estemos haciéndolo
  // a lo C++, uso sprintf() aquí porque es más rápido.
  sprintf(nom, "/proc/%d/oom_adj", getpid());

  // Abrimos el fichero
  fi.open(nom);
  if (fi.fail())
    error("No puedo abrir oom_adj");

  // Escribimos 15 en el fichero, para dar más "apeletas de muerte"
  // a este proceso. Ver:
  // http://totaki.com/poesiabinaria/2010/04/cuando-un-proceso-se-come-la-memoria-de-nuestro-sistema/
  fi<<"15";
  if (fi.fail())
    error("No puedo escribir oom_adj");

  fi.close();
}

Gastando memoria

Para esta parte hay que tener cuidado, ya que depende de la RAM que tengamos cada uno, yo he hecho esto en un equipo con 2GB de RAM, hasta que se ha vuelto extremadamente lento. Aunque el objetivo es que veamos la memoria que está actualmente en consumo. Podemos utilizar el comando free() en paralelo con el programa para ver que cada vez tenemos más memoria consumida, cuando no deberíamos, porque la intuición nos diría que cuando salimos de la función todo lo reservado se libera, pero no es así.
En los sistemas operativos actuales no podremos ponernos a reservar memoria sin control, ya que el SO casi siempre nos la dará (aunque no disponga de ella, dejando el marrón al gestor de memoria del futuro), si de verdad queremos reservar memoria, gastarla y darnos cuenta, debemos utilizar la memoria que reservamos; por ello he optado por rellenar un vector de números. Cuidado con subir mucho más MAX_NEW_ELEMENTS, es más, si tienes poca memoria, te recomiendo bajarla un poco, a la mitad, por ejemplo, en un primer cálculo, sólo contando los elementos del vector (8.000.000 x 8 (long int) = 64Mb), por lo que depende de lo que haya en ejecución, con cuatro llamadas a la función gasta_memoria() habremos gastado 256Mb de memoria.
Para ver lo que vamos gastando, debemos ejecutar en un terminal el programa que voy a mostrar, y en otro terminal ejecutamos free. El programa hará varias paradas pidiendo que pulsemos intro a cada llamada de gasta_memoria() para que podamos ver lo que se ha gastado, después del código veremos la ejecución.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <fstream>

#define MAX_NEW_ELEMENTS 8000000

using namespace std;

// Sale del programa devolviendo un error
void error(const char *mens);
// Ajunta oom_adjust para matar este proceso si se nos cuelga el sistema
// Así nos quitamos un disgusto
void ajusta_oom();
// Gasta memoria (casi) sin control
void gasta_memoria();
// Espera una tecla
void intro();

void gasta_memoria2()
{

  vector <long int> *v;


  v=new vector<long int>;

  for (unsigned long i=0; i<MAX_NEW_ELEMENTS; i++)
    v->push_back(i);

  intro();
  // delete v;
}


int main()
{
  ajusta_oom();
  cout << "OOM ajustado" << endl;
  intro();
  gasta_memoria();
  gasta_memoria2();
  gasta_memoria();
  gasta_memoria2();
}

void intro()
{
  char key;
  cout << "Pulse Intro"<<endl;
  cin.get(key);
}

void gasta_memoria()
{
  vector <long int> *v;

  v=new vector<long int>;

  for (unsigned long i=0; i<MAX_NEW_ELEMENTS; i++)
    v->push_back(i);

  intro();
  // delete v;
}

void error(const char *mens)
{
  cerr << "ERROR: ("<<mens<<") errno: "<<errno<<": "<<strerror(errno)<<endl;
  exit (1);
}

void ajusta_oom()
{
  ofstream fi;
  char nom[30];

  // Obtenemos el nombre del fichero, aunque estemos haciéndolo
  // a lo C++, uso sprintf() aquí porque es más rápido.
  sprintf(nom, "/proc/%d/oom_adj", getpid());

  // Abrimos el fichero
  fi.open(nom);
  if (fi.fail())
    error("No puedo abrir oom_adj");

  // Escribimos 15 en el fichero, para dar más "apeletas de muerte"
  // a este proceso. Ver:
  // http://totaki.com/poesiabinaria/2010/04/cuando-un-proceso-se-come-la-memoria-de-nuestro-sistema/
  fi<<"15";
  if (fi.fail())
    error("No puedo escribir oom_adj");

  fi.close();
}

La ejecución será escalonada, es decir, ejecutamos, y antes de pulsar intro ejecutamos free(), así vemos cómo se va gastando la memoria tal y como indica la imagen.
testi
Lo que quiero demostrar es que el puntero al vector v, cada vez que ejecutamos new vector sobre ella, restablece su valor, y por tanto, aunque la memoria asociada a ese vector sigue reservada (y encima tiene 8000000 de elementos), ya no tenemos forma de acceder a ella, ya no sabemos la dirección de memoria con la que accedemos; vamos que el hecho de que no sea accesible no significa que no esté reservada, en uso y molestando, porque el sistema no podrá utilizar esos datos para otras cosas.
En este caso, la solución a este problema habría sido escribir:

1
delete v;

al final de la función gasta_memoria(). Aunque en ocasiones esto no es tan fácil de ver.

Cuando el programa se cierra…

Generalmente el sistema operativo, que sí que sabe lo que el proceso ha reservado y consumido, libera la memoria, y a veces puede invertir un rato, aunque a veces no aparezca toda la memoria como libre directamente, al cabo de unos minutos (como mucho) ya estará listo.

Si el sistema no reacciona…

Recuerdo, que si hemos reservado mucha, mucha memoria, el sistema dejará de responder, para ello podemos pulsar Alt+SysRq+F, y el programa morirá rápidamente (pero, pulsad estas teclas, sólo una vez, y esperar).

Foto: tao_zhyn (Flickr) Compartido con CC-by a 26/11/2011

Polimorfismos: enrevesando la herencia entre clases [C++]

Sábado, 26 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Cuando desarrollamos bajo el paradigma de la programación orientada a objetos, a poco que avanzamos tenemos la sensación de estar escribiendo más para hacer lo mismo que hacíamos antes sin clases ni instancias ni tanta estructuración.
Pero contamos con herramientas que, aunque al principio nos sonarán algo chocantes, a la larga terminarán abriéndonos un mundo de posibilidades y comprenderemos por qué es tan importante, tan utilizado y se exige en multitud de universidades y centros educativos.
Uno de esos grandes avances que nos permite la programación orientada a objetos y que hoy particularizaremos a C++ son los polimorfismos.

Imaginemos (el ejemplo es clases_animalun poco tonto, lo reconozco, pero es lo más sencillo que se me ocurre para no llenar el post de código. Estoy abierto a sugerencias) que vamos a crear un software de mascota virtual, para eso construimos una clase padre (o superclase) llamada Animal. El sistema está en una fase muy temprana de desarrollo y los animales sólo pueden emitir sonidos (aunque en este programa se pondrá una onomatopeya por pantalla).

A partir de la clase Animal crearemos las clases Perro y Gato. Lo que necesitamos es que todos los miembros de Animal tengan un método hablar() que les permita emitir su sonido característico. Nuestra función main() queda así:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
  Mascota *p, *g;
 
  p=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << p->hablar() << endl;
  cout << g->hablar() << endl;

  return 0;
}

Lo que deseamos es que p (declarado como Mascota, pero en realidad es de clase Perro), diga “GUAU” y g (instancia de Gato) diga “MIAU”. Para eso necesitamos crear un polimorfismo (será un método que en cada subclase (clase heredera, o clase hija) ejecute el método correspondiente dependiendo de la clase de la instancia que tengamos. En C++ lo hacemos con métodos virtuales:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  virtual string hablar() = 0;

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string hablar();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::hablar()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string hablar();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::hablar()
{
  return "MIAU";
}

Cuando lo ejecutemos, una vez creados el Perro (Ramiro) y el Gato (Lancelot), cada uno dirá su palabra correspondiente. Para entender la diferencia de lo que origina utilizar virtual o no utilizarlo, lo mejor es probarlo. Vemos que para crear la función “virtual string hablar()” escribimos =0 (es una forma especial para que la función quede definida y no sólo declarada). Por tanto, para eliminar el virtual, debemos declarar la función como virtual string hablar() y definir su comportamiento. El fragmento quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::hablar()
{
  return "BUU";
}

En este caso, un “Animal por defecto” (que no pertenecería a ninguna especie) diría “BUU”, si ejecutamos el programa tal cual lo tenemos ahora, la salida sería:

Acaba de nacer tu mascota Ramiro con 4patas
Acaba de nacer tu mascota Lancelot con 4patas
BUU
BUU

¡ Los dos dirían BUU ! Y eso es porque inicialmente están declaradas como Animal, así que al llamar al método hablar() se llamará al de la clase Animal en lugar de Perro o Gato.

Ahora hacemos una pequeña modificación. Cambiamos los métodos virtual hablar() por palabra() y los ponemos en la parte privada, para el acceso desde main() creamos otra función hablar() no virtual en Mascota, cuyo contenido será:

1
2
3
4
string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

El objetivo es que cada animal, antes de decir su palabra escriba: “(minombre) dice: (mipalabra)”, en el caso del perro: “Ramiro dice: GUAU”. Aunque claro, es un método de Animal, por tanto this apuntaría a un Animal, que lo es, pero también es un Perro y como perro tiene su palabra característica y su función palabra() definida y propia, en la parte privada, por lo que intuitivamente no tendríamos acceso; pero virtual también funciona aquí para poder seleccionar el método hablar() de la clase correspondiente.

El resultado final queda así (por si quieres copiar y pegar):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  virtual string palabra();

  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::palabra()
{
  return "BUUU";
}

string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string palabra();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::palabra()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string palabra();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::palabra()
{
  return "MIAU";
}

int main()
{
  Mascota *m, *g;
 
  m=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << m->hablar() << endl;
  cout << g->hablar() << endl;
  return 0;
}

Nota: A veces también se llama polimorfismo a la sobrecarga (de métodos, por ejemplo, donde podemos describir varios métodos homónimos (todos llevarán el mismo nombre), pero con diferentes tipos de argumento, y en función de ellos, el compilador podrá saber a qué método llamamos. Todo esto tiene como función hacer la programación algo más intuitiva y natural.

Conociendo el proceso que me ha enviado una señal (signal)

Jueves, 24 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Hace tiempo hablábamos de capturar señales, aunque en ocasiones, es necesario saber quién me envía esa señal, si por ejemplo nos envían un SIGINT o SIGTERM, tal vez queremos saber qué proceso nos quiere muerto y qué usuario lo ha invocado. O tal vez estamos esperando una señal de control (SIGUSR1, por ejemplo) por parte de un proceso cliente específico.

El problema es que por nuestro modo actual de direccionar señales (con signal(señal, funcion)) sólo comunicamos el número de señal que se ha recibido, sin más información.

signals_from

Para capturar esta información necesitamos utilizar sigaction para establecer la función a la que llamaremos cuando llegue la señal, para utilizarla podemos utilizar el siguiente código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),
      signum,
      strsignal(signum),
      siginfo->si_pid,
      siginfo->si_uid,
      siginfo->si_errno);
}


int main(int argc, char *argv[])
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = recibe_signal;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    sigaction(SIGUSR1, &sact, NULL);

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause;

    return 0;
}

Como vemos sigaction tiene 3 parámetros:

  • La señal que queremos capturar
  • Un registro de tipo struct sigaction
  • La acción que se ejecutaba antes, por si queremos guardarla para restaurarla más tarde

Dentro del registro, tendremos como valores importantes (struct sigaction act):

  • sact.sa_sigaction : que especifica la función que la llamaremos, esta función será del tipo void funcion(int, siginfo_t*, void*), donde el primer int recibirá la señal que nos han enviado (tal y como hacía la función que asignábamos con signal), el segundo parámetro será información asociada a la señal, y la tercera el contexto de ejecución.
  • sact.sa_handler : será la función a la que se llama, es la misma que cuando hacemos signal(señal, funcion)
  • sact.sa_flags : Es un conjunto de flags, si especificamos SA_SIGINFO, estaremos dando paso a la función especificada por sa_sigaction, si no utilizaremos sa_handler. Aunque esta variable vale para más cosas, para esta práctica nos vale con saber esto. (man sigaction para más info)
  • sact.sa_mask
  • : Nos proporciona una máscara de las señales que serán bloquedas, podemos utilizar sigfillset() para rellenar toda la máscara automáticamente. Si queremos que unas se bloqueen y otras no podemos utilizar sigaddset() y sigdelset()

Ahora bien, la función que se llamará cuando venga una señal tendrá el prototipo que hemos visto antes con tres parámetros: la señal recibida, la información de la señal con un puntero a siginfo_t y el contexto de ejecución, que se pasará con un puntero a void, que por ahora no utilizaremos. La estructura que controla la información de la señal entre otros muchos datos nos facilitará lo siguiente:

  • si_pid : PID que nos envía la señal (y esta acción da título al post)
  • si_uid : Usuario que ejecuta el proceso que nos manda la señal
  • si_errno : Algún error que haya causado la señal
  • … para más información, man sigaction

Para preparar la sentencia tendremos que escribir varias líneas definiendo los valores del registro así como la llamada a la función sigaction(), por lo tanto podremos crear una función que tenga los parámetros más comunes a la hora de definir este tipo de acciones, así lo llamamos como a signal():

1
2
3
4
5
6
7
8
9
10
11
12
int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

En esta función es importante poner bien el prototipo de la función como parámetro, eso lo comenté en un post anterior sobre callbacks

Ahora, vamos a dejar el código más bonito, este programa implementará el control de errores y nos permitirá salir cuando recibamos 5 SIGINT (pulsemos 5 veces control+C):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void error(char *cadena);
int sigact(int signum, void funcion(int, siginfo_t *, void *));
void llegasigint();
void recibe_signal(int signum, siginfo_t *siginfo, void *context);

int main(int argc, char *argv[])
{
  if (sigact(SIGUSR1, recibe_signal)==-1)
    error("No puedo establecer SIGUSR1");

  if (sigact(SIGINT, recibe_signal)==-1)
    error("No puedo establecer SIGINT");
  if (sigact(SIGTERM, recibe_signal)==-1)
    error("No puedo establecer SIGTERM");
  if (sigact(SIGUSR2, recibe_signal)==-1)
    error("No puedo establecer SIGUSR2");
  if (sigact(SIGQUIT, recibe_signal)==-1)
    error("No puedo establecer SIGQUIT");

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause();

    return 0;
}

void error(char *cadena)
{
  fprintf(stderr, "ERROR: %s (errno: %d: %s)\n", cadena, errno, strerror(errno));
  exit(1);
}

void llegasigint()
{
  static int num=0;     /* No he querido usar variables globales */

  num++;
  if (num==5)
    {
      printf ("FIN del programa\n");
      exit(1);
    }
}

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),     /* PID del proceso actual */
      signum,       /* Número de señal recibida */
      strsignal(signum),    /* Señal escrita en texto (inglés) */
      siginfo->si_pid,  /* PID del proceso que nos manda la señal */
      siginfo->si_uid,  /* Usuario que nos manda la señal */
      siginfo->si_errno);   /* Error producido */

  if (signum==SIGINT)
    llegasigint();
}

int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

RIP Dennis Ritchie

Domingo, 16 de Octubre de 2011 Gaspar Fernández 2 comentarios

Muchos medios de comunicación y blogs ( 1, 2, 3, 4, 5 por ejemplo ) ya informaron de la noticia. Es una lástima que una de las grandes mentes del siglo XX y parte del XXI pasara tan desapercibida, y es que nos enteramos de la noticia hace 3 días habiendo sucedido hace una semana. Y es que sin él, este blog no existiría (C / C++ / PHP (hecho en C) / Linux (tipo Unix)), tal vez muchas de las cosas que hacemos en el ordenador no serían posibles, a lo mejor ni tendríamos ordenadores en casa, ni MP3, ni gadgets, ni smartphones, tal vez sea demasiado decir, puede que en un mundo paralelo todo esto exista sin su aportación lo que es seguro es que nada de esto sería como lo conocemos.

Dennis Ritche dejó su huella en el mundo de la informática, y una gran huella, si pudiéramos contar cuánto tiempo invirtió en su vida aprendiendo todo lo que tuvo que aprender y trabajando en su legado a la humanidad y todo el tiempo que el resto de la humanidad ha invertido utilizándolo estaríamos ante una persona situada en el Top 10 de la edad contemporánea. Desde aquí, le doy las gracias.

Después de enterarnos de esta pérdida, es inevitable compararla con la muerte de Steve Jobs, pocos días antes, alguien que sin duda, también ha hecho mucho por la tecnología, aunque se movieron en campos diferentes, uno nos proporcionó la base y las herramientas, otro utilizó esas herramientas para construir algo mayor y ver futuro en cosas que nadie consideraba útiles.

Y pensar que hace unos días estuve hablando de él en otro blog.
¡Descanse en paz Dennis Ritchie!

Ocultar los parámetros de nuestro proceso a ps

Martes, 11 de Octubre de 2011 Gaspar Fernández Sin comentarios

password_flickrEn ocasiones, estamos desarrollando una aplicación, y ésta necesita que le pasemos como parámetro, por ejemplo, una contraseña. El gran peligro que esto tiene es que cualquier usuario, pidiendo un listado de procesos con ps podrá ver la contraseña.

Imaginemos que tenemos una aplicación (que hemos hecho nosotros) que conecta con un servidor, y dado que hemos hecho un script para automatizar el proceso, el nombre de usuario y contraseña los pasaremos como parámetro al ejecutable. Dicho proceso se llama “aplicacionsegura”, dicho programa lo hemos lanzado en un servidor y otros usuarios tendrán acceso a dicha máquina. Ahora uno de ellos ejecuta lo siguiente:

$ ps ax
….
10560 pts/3    S+     0:00 ./aplicacionsegura –user=usuario –passwd=micontraseña

¡Nuestro gozo en un pozo! Otro usuario ya tiene nuestra contraseña, y ha sido muy sencillo. Y además lo podremos ver (de hecho ps es lo que hace) viendo el archivo /proc/PID/cmdline. Aunque debemos ver qué opciones tenemos:

Modificar el parámetro passwd

Para ilustrar el ejemplo, vamos a fijar la posición de –passwd=xxxxx al segundo parámetro (la captura de parámetros no es objeto de este post). Lo primero que vamos a probar es introducir un terminador en las cadenas de los parámetros, Si la cadena empieza por ‘\0′ estará vacía:

1
2
3
4
...
argv[1][0]='\0';
argv[2][0]='\0';
....

Aunque tardaremos poco en darnos cuenta que da igual, por una parte, no podríamos borrar todos los rastros de información, ya que si escribimos en la posición 0 de la cadena “–passwd=micontraseña” un terminador, el resto de la cadena se quedará en memoria y podrá ser leída. De todas formas, la cadena parece que sólo tiene un espacio para ps, es más, el \0 le ha dado igual.

Probemos ahora escribiendo “HOLA” en la cadena de la contraseña (ahora sí pongo el código completo, test.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
  /* Reservamos memoria de sobra */
  char password[40];
  char usuario[40];

  if (argc<3)
    {
      fprintf(stderr,"Debe tener dos parámetros:\n%s --user=USUARIO --passwd=CLAVE\n", argv[0]);
      return EXIT_FAILURE;
    }

  /* Esto es un simple ejemplo, vamos a quitar --user y --passwd de una forma fea */

  /* Copiamos el usuario en otra variable */
  strcpy(usuario, argv[1]+7);   /* +7, para eliminar --user= (7 caracteres) */

  /* Copiamos la contraseña en otra variable */
  strcpy(password, argv[2]+9);  /* +9, para eliminar --passwd= (9 caracteres) */

  strcpy(argv[2]+9, "HOLA"); // EN LA CLAVE PONEMOS "HOLA"

  printf("user: %s ; pass: %s", usuario, password);
  /* Esperamos una tecla para terminar (sólo para que no se cierre demasiado rápido */
  /* y nos dé tiempo a mirar la información de ps */
  getchar();
  return EXIT_SUCCESS;
}

Ahora ejecutaremos lo siguiente:

$ gcc -o test test.c
$ ./test & # El programa test esperará una tecla para terminar, pero lo ejecutaremos de fondo, para que no se cierre.
$ ps ax
….
10809 pts/3 S+ 0:00 ./test –user=usuario –passwd=HOLA traseña
….
$ killall test # Para matar el proceso

Un efecto curioso, las cadenas de los parámetros no son NULL-terminated, de cara a /proc/PID/cmdline por lo que cuando ponemos un terminador, lo toma como un espacio. Y se ve parte del parámetro. Lo que sí podemos hacer es sustituir por asteriscos, tantos asteriscos como letras tenga el parámetro.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void rellenaConAsteriscos(char *cadena);

int main(int argc, char *argv[])
{
  /* Reservamos memoria de sobra */
  char password[40];
  char usuario[40];

  if (argc<3)
    {
      fprintf(stderr,"Debe tener dos parámetros:\n%s --user=USUARIO --passwd=CLAVE\n", argv[0]);
      return EXIT_FAILURE;
    }

  /* Esto es un simple ejemplo, vamos a quitar --user y --passwd de una forma fea */

  /* Copiamos el usuario en otra variable */
  strcpy(usuario, argv[1]+7);   /* +7, para eliminar --user= (7 caracteres) */

  /* Copiamos la contraseña en otra variable */
  strcpy(password, argv[2]+9);  /* +9, para eliminar --passwd= (9 caracteres) */

  rellenaConAsteriscos(argv[1]+7);
  rellenaConAsteriscos(argv[2]+9);

  printf("user: %s ; pass: %s", usuario, password);
  /* Esperamos una tecla para terminar (sólo para que no se cierre demasiado rápido */
  /* y nos dé tiempo a mirar la información de ps */
  getchar();
  return EXIT_SUCCESS;
}

void rellenaConAsteriscos(char *cadena)
{
  while (*cadena!='\0')
    *(cadena++)='*';
}

Ahora al ejecutar ps:

$ ps ax

10906 pts/3 S+ 0:00 ./test –user=******* –passwd=*************

Aunque esta forma revela en cierto modo la longitud de las contraseñas y el nombre de usuario, puede que no nos interese, que queramos eliminar el parámetro entero y que no se vea:

La manera rápida pero peligrosa y de andar por casa

El objetivo es escribir algo más largo que el último parámetro. Es decir, si el parámetro de contraseña tiene 22 letras, tenemos que escribir algo más grande, unas 23 letras, o cargarnos el terminador del segundo argumento (no son NULL-terminated, el kernel sabe dónde empieza y dónde termina la cadena, pero hay un NULL para comprobar que todo va bien).

En este caso podemos hacer:

1
strcpy(argv[2]+9, "Esto es una cadena muy grande para ver si cargándome el segundo parámetro desaparece toda la información");

y veremos cómo ps no nos dice nada, en principio no es demasiado elegante, nos estamos cargando un terminador, que nosotros podríamos necesitar, para lectura, y estamos escribiendo en una zona de memoria que no es nuestra, ya que el argumento tiene reservado un tamaño de memoria, y estamos escribiendo más de lo que debemos (podemos producir una violación de segmento).

Decirle al kernel que no queremos los parámetros

Parecida a la forma anterior, podemos hacer:

1
argv[2][strlen(argv[2])]=' ';

lo que no es intuitivo para nada, porque nos estamos cargando el terminador, que controla el paso de parámetros. De esta forma, no tendremos ningún parámetro. Tambén podemos hacer una forma general

1
argv[argc-1][strlen(argv[argc-1])]=' ';

De esta forma desaparecerán todos los argumentos de /proc/PID/cmdline ; sólo tendremos la ruta y el nombre del ejecutable, y nuestros datos, al menos por esta parte, estarán seguros.
Foto: marc falardeau (Flickr)

Buscando un proceso en C

Martes, 4 de Octubre de 2011 Gaspar Fernández 2 comentarios

Comando topEn ocasiones, nuestros programas requieren que un servicio o un programa esté en ejecución. Algunos servicios los podemos ubicar fácilmente, ya que /var/run ,  /dev/shm u otra ruta contienen un archivo con su PID (Identificador de proceso), otras servicios no figuran en ningún lado. También puede ser que estemos esperando que otro proceso termine y necesitamos averiguar su PID.

Para todo ello, debemos echar un ojo al contenido de /proc/, ahí encontramos, entre otras cosas, información sobre los procesos en ejecución del sistema. En principio lo que nos interesa es el nombre del ejecutable (es lo que estamos buscando), para ello, vamos a buscar dentro del directorio /proc/ todos los directorios que sean numéricos (esos que contendrán un PID), y leeremos el fichero /proc/[pid]/comm, que contiene la información del nombre del ejecutable. Eso sí, ese nombre tendrá una longitud limitada (marcada por la constante del kernel TASK_COMM_LEN, que suele valer 16 (con lo que los nombres tendrán como máximo 15 caracteres):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>

void tareaProceso(char *nombre, int pid);

void buscaProceso(char *nombre);

void error(char *error);

/* Una función lo más general posible */
void getProcName(char *dest, int pid);

int main(int argc, char *argv[])
{
  buscaProceso("firefox-bin");
  return EXIT_SUCCESS;
}

void tareaProceso(char *nombre, int pid)
{
  printf("Encontrado proceso %-16s con PID: %d\n", nombre, pid);
  /* Aquí ya puedo matarlo, mandarle señales o hacer lo que quiera */
  /* con el proceso en cuestión */
}

void buscaProceso(char *nombre)
{
  DIR *proc;
  struct dirent *proceso;
  char procname[16];        /* Nombre del proceso */
  int pid;

  proc=opendir("/proc");

  if (proc==NULL)
    error("No puedo acceder al directorio /proc");

  while ((proceso=readdir(proc)) != NULL)
    {
      /* En /proc hay más cosas además de los PIDs, debemos asegurarnos de que */
      /* lo que hemos visto es un ID de proceso, lo demás no nos interesa */
      if ( (*proceso->d_name>'0') && (*proceso->d_name<='9') )
    {
      /* Convertimos el PID a número para la llamada a la función */
      pid=strtol(proceso->d_name, NULL, 10);
      getProcName(procname, pid);
      if (strncmp(nombre,procname,15)==0)
        tareaProceso(procname, pid);
    }
    }
}

void error(char *error)
{
  fprintf(stderr, "Error: %s (%d, %s)\n", error, errno, strerror(errno));
  exit(EXIT_FAILURE);
}

void getProcName(char *dest, int pid)
{
  FILE *fcomm;
  char filename[20];        /* /proc/[PID] (5 chars)/comm : total 16 chars */

  sprintf(filename,"/proc/%d/comm", pid);

  fcomm=fopen(filename, "r");
  if (!fcomm)
    error("No existe el fichero");
  /* A veces fgets() coge un salto de línea extra */
  /* fgets(dest, 16, fcomm); */
  fscanf(fcomm, "%s", dest);
  close(fcomm);
}

En este ejemplo si comentamos la línea 52 y en la línea 53 ponemos:

1
tareaProceso(nombre, pid);

podemos obtener un listado completo de las tareas en ejecución (sólo por curiosear un poco). Aunque en este ejemplo tenemos un problema, mencionado antes, el número de caracteres del ejecutable, es decir, hay ejecutables con más de 15 caracteres, y tal vez queramos buscarlos. Con la solución actual estamos cortando los nombres (con strncmp()), es decir, sería lo mismo aplicacionconnombrelargo que aplicacionconnombrecorto, ya que sólo tenemos en cuenta los primeros 15 caracteres. Tiene que haber otra forma de sacar el nombre completo, y es a través del archivo /proc/[PID]/cmdline, es más, desde aquí podemos sacar la ruta del ejecutable y los parámetros con los que se llamó, tal y como vemos cuando ejecutamos:

$ ps x

Vamos a ver cómo podemos adaptar el código de antes para poder sacar toda esa información.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <libgen.h>     /* basename */

enum pnametype
  {
    SHORT,
    LONG,
    WPATH
  };

/* Realiza una tarea con el proceso encontrado */
void tareaProceso(char *nombre, int pid);

/* Busca un proceso determinado */
void buscaProceso(char *nombre);

/* Sale del programa emitiendo un error */
void error(char *error);

/* Obtiene el nombre de un proceso */
void getProcName(char *dest, int pid, enum pnametype outtype);

/* Lee los contenidos de un archivo */
int getFileContents(char *dest, char *fname);

int main(int argc, char *argv[])
{
  buscaProceso("plugin-container");
  return EXIT_SUCCESS;
}

void tareaProceso(char *nombre, int pid)
{
  printf("Encontrado proceso %-16s con PID: %d\n", nombre, pid);
  /* Aquí ya puedo matarlo, mandarle señales o hacer lo que quiera */
  /* con el proceso en cuestión */
}

void buscaProceso(char *nombre)
{
  DIR *proc;
  struct dirent *proceso;
  char procname[255];       /* Como getProcName necesita una variable grande, se la damos */
  int pid;

  proc=opendir("/proc");

  if (proc==NULL)
    error("No puedo acceder al directorio /proc");

  while ((proceso=readdir(proc)) != NULL)
    {
      /* En /proc hay más cosas además de los PIDs, debemos asegurarnos de que */
      /* lo que hemos visto es un ID de proceso, lo demás no nos interesa */
      if ( (*proceso->d_name>'0') && (*proceso->d_name<='9') )
    {
      /* Convertimos el PID a número para la llamada a la función */
      pid=strtol(proceso->d_name, NULL, 10);
      getProcName(procname, pid, LONG);
      if (strcmp(nombre,procname)==0)
        tareaProceso(procname, pid);
    }
    }
}

void error(char *error)
{
  fprintf(stderr, "Error: %s (%d, %s)\n", error, errno, strerror(errno));
  exit(EXIT_FAILURE);
}

int getFileContents(char *dest, char *fname)
{
  FILE *fdata;
  int res;

  fdata=fopen(fname, "r");
  if (!fdata)
    error("No existe el fichero");
 
  res=fscanf(fdata, "%s", dest);
  if (res==EOF)         /* Si no hemos leído nada,  */
    *dest='\0';         /* nos aseguramos de vaciar la cadena */

  fclose(fdata);

  /* Devolvemos un error si, por ejemplo el fichero está vacío */
  return (res!=EOF);
}
/* dest tiene que ser grande, en un proyecto real, reservamos memoria desde */
/* getProcName, no nos arriesgamos a que falle algo de fuera */
void getProcName(char *dest, int pid, enum pnametype outtype)
{
  char filename[20];        /* /proc/[PID] (5 chars)/cmdline : total 19 chars */
  char *tmp;
  char *newfile;
  if (outtype==SHORT)
    sprintf(filename,"/proc/%d/comm", pid);
  else
    sprintf(filename,"/proc/%d/cmdline", pid);

  if (!getFileContents(dest, filename))
    {
      /* Si no hemos podido obtener nada */
      if (outtype!=SHORT)
    {
      /* probamos desde comm */
      sprintf(filename,"/proc/%d/comm", pid);
      getFileContents(dest, filename);
    }
    }

  if (outtype!=SHORT)
    {
      /* Si queremos un tipo de salida que no sea el corto, tendremos que transformar esta cadena */
      tmp=dest;
      while ((*tmp!=' ') && (*tmp!='\0')) ++tmp;
      tmp='\0';
      if (outtype==LONG)
    {
      tmp=basename(dest);
      strcpy(dest, tmp);
    }
    }
}

Ahora, obtenemos la información dependiendo del tipo especificado por outtype de tipo enum pnametype, e incluso si especificamos LONG, este tipo extraerá el nombre del archivo ejecutable despreciando la ruta del mismo. Para descargar los archivos de código, click aquí (2.3Kb)

Listar archivos dentro de un directorio o carpeta en C

Martes, 27 de Septiembre de 2011 Gaspar Fernández 4 comentarios

Árbol de directoriosNuestro software debe buscar archivos dentro de un directorio determinado, ya sea un archivo especial, una recopilación de datos del disco duro, una búsqueda de plugins, etc…

Hemos elegido lenguaje C para hacer esto, porque en bash podemos llamar a ls o find y apaga y vámonos.

Estas funciones, nos recordarán al uso de archivos con fopen() y fclose().

Para usar esta implementación, como veremos en el ejemplo, debemos incluir <sys/types.c> y <dirent.h> y, en principio, empezaremos con un ejemplo sencillo que lista los archivos del directorio actual (una vez tenemos el nombre y la ruta del archivo podemos hacer con él lo que queramos):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);
/* Función que hace algo con un archivo */
void procesoArchivo(char *archivo);

int main()
{
  /* Con un puntero a DIR abriremos el directorio */
  DIR *dir;
  /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */
  struct dirent *ent;

  /* Empezaremos a leer en el directorio actual */
  dir = opendir (".");

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  /* Una vez nos aseguramos de que no hay error, ¡vamos a jugar! */
  /* Leyendo uno a uno todos los archivos que hay */
  while ((ent = readdir (dir)) != NULL)
    {
      /* Nos devolverá el directorio actual (.) y el anterior (..), como hace ls */
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      /* Una vez tenemos el archivo, lo pasamos a una función para procesarlo. */
      procesoArchivo(ent->d_name);
    }
    }
  closedir (dir);

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

void procesoArchivo(char *archivo)
{
  /* Para "procesar", o al menos, hacer algo con el archivo, vamos a decir su tamaño en bytes */
  /* para ello haremos lo que vemos aquí: http://totaki.com/poesiabinaria/2010/04/tamano-de-un-fichero-en-c/ */
  FILE *fich;
  long ftam;

  fich=fopen(archivo, "r");
  if (fich)
    {
      fseek(fich, 0L, SEEK_END);
      ftam=ftell(fich);
      fclose(fich);
      /* Si todo va bien, decimos el tamaño */
      printf ("%30s (%ld bytes)\n", archivo, ftam);
    }
  else
    /* Si ha pasado algo, sólo decimos el nombre */
    printf ("%30s (No info.)\n", archivo);
}

Este es un ejemplo sencillo, podemos profundizar un poco más. Vemos que readdir() nos devuelve todo lo que hay, y a veces, es muy útil diferenciar entre un fichero, un directorio, un enlace, un socket, un fifo, etc… para eso, en un principio utilizaremos la información del mismo dirent, que a veces la encontramos de forma rápida, aunque existen sistemas de archivos que no nos facilitan esa información de forma tan inmediata y tenemos que hacer stat(), un ejemplo de ello es nfs, ya que es significativamente más lento obtener esa información, si no la necesitamos, no intenta obtenerla, es para esos casos cuando obligamos a su obtención con stat().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);

/* Calculamos el tamaño del archivo */
long fileSize(char *fname);

/* Sacamos el tipo de archivo haciendo un stat(), es como el stat de la línea de comandos */
unsigned char statFileType(char *fname);

/* Función que hace algo con un archivo, pero le pasamos el dirent completo, usaremos más datos */
void procesoArchivo(char *ruta, struct dirent *ent);

int main(int argc, char *argv[])
{
  /* Con un puntero a DIR abriremos el directorio */
  DIR *dir;
  /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */
  struct dirent *ent;

  if (argc != 2)
    {
      error("Uso: ./directorio_2 <ruta>\n");
    }
  /* Empezaremos a leer en el directorio actual */
  dir = opendir (argv[1]);

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  /* Una vez nos aseguramos de que no hay error, ¡vamos a jugar! */
  /* Leyendo uno a uno todos los archivos que hay */
  while ((ent = readdir (dir)) != NULL)
    {
      /* Nos devolverá el directorio actual (.) y el anterior (..), como hace ls */
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      /* Una vez tenemos el archivo, lo pasamos a una función para procesarlo. */
      procesoArchivo(argv[1], ent);
    }
    }
  closedir (dir);

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

long fileSize(char *fname)
{
  FILE *fich;
  long ftam=-1;

  fich=fopen(fname, "r");
  if (fich)
    {
      fseek(fich, 0L, SEEK_END);
      ftam=ftell(fich);
      fclose(fich);
    }
  else
    printf("ERRNO: %d - %s\n", errno, strerror(errno));
  return ftam;
}

void procesoArchivo(char *ruta, struct dirent *ent)
{
  long ftam;
  char *nombrecompleto;
  char strtam[20];
  char strtipo[30]="";
  /* Tiene que ser del mismo tipo de dirent.d_type en nuestro sistema */
  static unsigned char tipoID[7]={DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK};
  static char* tipoSTRs[7]={"Dispositivo de bloques", "Dispositivo de caracteres", "Directorio", "FIFO", "Enlace", "Archivo regular", "Socket Unix"};

  int i;
  int tmp;
  unsigned char tipo;

  /* Sacamos el nombre completo con la ruta del archivo */
  tmp=strlen(ruta);
  nombrecompleto=malloc(tmp+strlen(ent->d_name)+2); /* Sumamos 2, por el \0 y la barra de directorios (/) no sabemos si falta */
  if (ruta[tmp-1]=='/')
    sprintf(nombrecompleto,"%s%s", ruta, ent->d_name);
  else
    sprintf(nombrecompleto,"%s/%s", ruta, ent->d_name);

  /* Calcula el tamaño */
  ftam=fileSize(nombrecompleto);
  if (ftam>=0)
    sprintf(strtam, "%ld bytes", ftam);
  else
    strcpy(strtam, "No info");

  /* A veces ent->d_type no nos dice nada, eso depende del sistema de archivos que estemos */
  /* mirando, por ejemplo ext*, brtfs, sí nos dan esta información. Por el contrario, nfs */
  /* no nos la da (directamente, una vez que hacemos stat sí lo hace), y es en estos casos donde probamos con stat() */
  tipo=ent->d_type;
  if (tipo==DT_UNKNOWN)
    tipo=statFileType(nombrecompleto);

  if (tipo!=DT_UNKNOWN)
    {
      /* Podíamos haber hecho un switch con los tipos y devolver la cadena,
         pero me da la impresión de que así es menos costoso de escribir. */

      i=0;
      while ( (i<7) && (tipo!=tipoID[i]) )
    ++i;

      if (i<7)
    strcpy(strtipo, tipoSTRs[i]);
    }

  /* Si no hemos encontrado el tipo, éste será desconocido */
  if (strtipo[0]=='\0')
    strcpy(strtipo, "Tipo desconocido");

  printf ("%30s (%s)\t%s \n", ent->d_name, strtam, strtipo);

  free(nombrecompleto);
}

/* stat() vale para mucho más, pero sólo queremos el tipo ahora */
unsigned char statFileType(char *fname)
{
  struct stat sdata;

  /* Intentamos el stat() si no funciona, devolvemos tipo desconocido */
  if (stat(fname, &sdata)==-1)
    {
      return DT_UNKNOWN;
    }


  switch (sdata.st_mode & S_IFMT)
    {
    case S_IFBLK:  return DT_BLK;
    case S_IFCHR:  return DT_CHR;
    case S_IFDIR:  return DT_DIR;
    case S_IFIFO:  return DT_FIFO;
    case S_IFLNK:  return DT_LNK;
    case S_IFREG:  return DT_REG;
    case S_IFSOCK: return DT_SOCK;
    default:       return DT_UNKNOWN;
    }
}

Un ejemplo más grande, contamos los archivos que hay en un árbol de directorios. Uno a uno, pero de forma recursiva (cuando encontramos un directorio nos meteremos dentro y llamaremos de nuevo a la función principal (cuentaArchivos()):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);

/* Calculamos el tamaño del archivo */
long fileSize(char *fname);

/* Sacamos el tipo de archivo haciendo un stat(), es como el stat de la línea de comandos */
unsigned char statFileType(char *fname);

/* Intenta sacar el tipo de archivo del ent */
unsigned char getFileType(char *ruta, struct dirent *ent);

/* Obtiene el nombre del fichero con la ruta completa */
char *getFullName(char *ruta, struct dirent *ent);

/* Genera una cadena de espacios, para dibujar el árbol */
char *generaPosStr(int niv);

/* Función principal, que cuenta archivos */
unsigned cuentaArchivos(char *ruta, int niv);

int main(int argc, char *argv[])
{
  unsigned num;

  if (argc != 2)
    {
      error("Uso: ./directorio_2 <ruta>\n");
    }
  printf("Entrando en: %s\n", argv[1]);
  num=cuentaArchivos(argv[1], 1);
  printf("%s . Total: %u archivos\n", argv[1], num);
  /* Empezaremos a leer en el directorio actual */

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

char *getFullName(char *ruta, struct dirent *ent)
{
  char *nombrecompleto;
  int tmp;

  tmp=strlen(ruta);
  nombrecompleto=malloc(tmp+strlen(ent->d_name)+2); /* Sumamos 2, por el \0 y la barra de directorios (/) no sabemos si falta */
  if (ruta[tmp-1]=='/')
    sprintf(nombrecompleto,"%s%s", ruta, ent->d_name);
  else
    sprintf(nombrecompleto,"%s/%s", ruta, ent->d_name);
 
  return nombrecompleto;
}

char *generaPosStr(int niv)
{
  int i;
  char *tmp=malloc(niv*2+1);    /* Dos espacios por nivel más terminador */
  for (i=0; i<niv*2; ++i)
    tmp[i]=' ';
  tmp[niv*2]='\0';
  return tmp;
}

unsigned cuentaArchivos(char *ruta, int niv)
{
  /* Con un puntero a DIR abriremos el directorio */
  DIR *dir;
  /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */
  struct dirent *ent;
  unsigned numfiles=0;          /* Ficheros en el directorio actual */
  unsigned char tipo;       /* Tipo: fichero /directorio/enlace/etc */
  char *nombrecompleto;     /* Nombre completo del fichero */
  char *posstr;         /* Cadena usada para posicionarnos horizontalmente */
  dir = opendir (ruta);

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  while ((ent = readdir (dir)) != NULL)
    {
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      nombrecompleto=getFullName(ruta, ent);
      tipo=getFileType(nombrecompleto, ent);
      if (tipo==DT_REG)
        {
          ++numfiles;
        }
      else if (tipo==DT_DIR)
        {
          posstr=generaPosStr(niv);
          printf("%sEntrando en: %s\n", posstr, nombrecompleto);         
          printf("%s%s . Total: %u archivos ", posstr, nombrecompleto, cuentaArchivos(nombrecompleto, niv+1));
          /* Podemos poner las líneas que queramos */
          printf("\n");
          free(posstr);
        }
      free(nombrecompleto);
    }
    }
  closedir (dir);
 
  return numfiles;
}

unsigned char getFileType(char *nombre, struct dirent *ent)
{
  unsigned char tipo;

  tipo=ent->d_type;
  if (tipo==DT_UNKNOWN)
    {
      tipo=statFileType(nombre);
    }

  return tipo;
}

/* stat() vale para mucho más, pero sólo queremos el tipo ahora */
unsigned char statFileType(char *fname)
{
  struct stat sdata;

  /* Intentamos el stat() si no funciona, devolvemos tipo desconocido */
  if (stat(fname, &sdata)==-1)
    {
      return DT_UNKNOWN;
    }

  switch (sdata.st_mode & S_IFMT)
    {
    case S_IFBLK:  return DT_BLK;
    case S_IFCHR:  return DT_CHR;
    case S_IFDIR:  return DT_DIR;
    case S_IFIFO:  return DT_FIFO;
    case S_IFLNK:  return DT_LNK;
    case S_IFREG:  return DT_REG;
    case S_IFSOCK: return DT_SOCK;
    default:       return DT_UNKNOWN;
    }
}

Se puede descargar todo el código fuente desde aquí: Listado de archivos dentro de un directorio en C (3.5Kb)

Actualización (14/10/2011): En el blog código para llevar encontramos un ejemplo de cómo utilizar este código para encontrar el número de núcleos de nuestra CPU.

C.I. XII: La evolución de la web, 20 aniversario de Linux, DateTime para php, instalar Arch Linux, Creando una aplicación en Android y un pedal con Arduino

Domingo, 4 de Septiembre de 2011 Gaspar Fernández Sin comentarios

Me gustaría esta vez compartir algunos enlaces que me han llamado la atención:

Categories: Arduino, C/C++, General, Linux, PHP, curioso Tags:

Visita otras webs de la red