Por que não unsetenv () modifica / proc / pid / environ?

5

Eu estava olhando para esta questão e escrevi um programa noddy para demonstrar unsetenv() modificando /proc/pid/environ . Para minha surpresa, não tem efeito!

Veja o que eu fiz:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
  printf("pid=%d\n", getpid());
  printf("sleeping 10...\n");
  sleep(10);
  printf("unsetenv result: %d\n", unsetenv("WIBBLE"));
  printf("unset; sleeping 10 more...\n");
  sleep(10);

  return 0;
}

No entanto, quando eu corro

WIBBLE=hello ./test_program

vejo WIBBLE no ambiente antes e depois do unsetenv() executado:

# before the unsetenv()
$ tr '
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
  printf("pid=%d\n", getpid());
  printf("sleeping 10...\n");
  sleep(10);
  printf("unsetenv result: %d\n", unsetenv("WIBBLE"));
  printf("unset; sleeping 10 more...\n");
  sleep(10);

  return 0;
}
' '\n' < /proc/498/environ | grep WIBBLE WIBBLE=hello # after the unsetenv() $ tr '
WIBBLE=hello ./test_program
' '\n' < /proc/498/environ | grep WIBBLE WIBBLE=hello

Por que não unsetenv() modify / proc / pid / environ?

    
por Flup 12.08.2016 / 11:39

1 resposta

9

Quando um programa é iniciado, ele recebe seu ambiente como uma matriz de ponteiros para algumas strings no formato var=value . No Linux, esses estão localizados na parte inferior da pilha. Na parte inferior, você tem todas as strings dobradas uma após a outra (isso é mostrado em /proc/pid/environ ). E acima você tem uma matriz de ponteiros (NULL terminados) para essas cadeias (é isso que entra em char *envp[] em seu int main(int argc, char* argv[], char* envp[]) , e a libc geralmente inicializa environ para).

putenv() / setenv() / unsetenv() , não modifique essas cadeias, elas nem geralmente modificam os ponteiros. Em alguns sistemas, esses (strings e ponteiros) são somente leitura.

Enquanto a libc geralmente inicializa char **environ para o endereço do primeiro ponteiro acima, qualquer modificação do ambiente (e essas são para futuros executivos), geralmente fará com que uma nova matriz de ponteiros seja criada e atribuída a environ .

Se environ for inicialmente [a,b,c,d,NULL] , onde a é um ponteiro para x=1 , b para y=2 , c para z=3 , d para q=5 , se você faz um unsetenv("y") , environ teria que se tornar [a,c,d,NULL] . Nos sistemas em que a lista inicial de matriz é somente leitura, uma nova lista teria que ser alocada e atribuída a environ e [a,c,d,NULL] armazenadas nela. No próximo unsetenv() , a lista poderia ser modificada no lugar. Somente se você tiver unsetenv("x") acima, uma lista não poderá ser realocada ( environ poderia ser incrementado para apontar para &envp[1] . Não sei se algumas implementações de libc realmente executam essa otimização).

Em qualquer caso, não há motivo para que as cadeias armazenadas na parte inferior da pilha sejam modificadas de qualquer forma. Mesmo que uma implementação de unsetenv() estivesse realmente modificando os dados inicialmente recebidos na pilha no local, isso apenas modificaria os ponteiros, não haveria todo o trabalho de também apagar as seqüências apontadas. (que parece ser o que o GNU libc faz em sistemas Linux (com executáveis ELF, pelo menos), ele modifica a lista de ponteiros em envp no lugar, desde que o número de variáveis de ambiente não aumente.

Você pode observar o comportamento usando um programa como:

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char* argv[], char* envp[]) {
  char cmd[128];
  int i;

  printf("envp: %p environ: %p\n", envp, environ);
  for (i = 0; envp[i]; i++)
    printf("  envp[%d]: %p (%s)\n", i, envp[i], envp[i]);

#define DO(x) x; puts("\nAfter " #x "\n"); \
  printf("envp: %p environ: %p\n", envp, environ); \
  for (i = 0; environ[i]; i++) \
    printf("  environ[%d]: %p (%s)\n", i, environ[i], environ[i])

  DO(unsetenv("a"));
  DO(setenv("b", "xxx", 1));
  DO(setenv("c", "xxx", 1));

  puts("\nAddress of heap and stack:");
  sprintf(cmd, "grep -e stack -e heap /proc/%u/maps", getpid());

  fflush(stdout);
  system(cmd);
}

No Linux com o GNU libc (mesmo com klibc, musl libc ou dietlibc, exceto pelo fato de usar memória anônima mmap em vez do heap para memória alocada), quando executado como env -i a=1 x=3 ./e , que fornece (comentários inline) :

envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
  envp[0]: 0x7ffc2e7b4fec (a=1)
  envp[1]: 0x7ffc2e7b4ff0 (x=3)
   # envp[1] is almost at the bottom of the stack. I lied above in that
   # there are more things like the path of the executable
   # environ initially points to the same pointer list as envp

After unsetenv("a")

envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
  environ[0]: 0x7ffc2e7b4ff0 (x=3)
   # here, unsetenv has reused the envp[] list and has not allocated a new
   # list. It has shifted the pointers though and not done the optimisation
   # I mention above

After setenv("b", "xxx", 1)

envp: 0x7ffc2e7b3238 environ: 0x1bb3420
  environ[0]: 0x7ffc2e7b4ff0 (x=3)
  environ[1]: 0x1bb3440 (b=xxx)
   # a new list has been allocated on the heap. (it could have reused the
   # slot freed by unsetenv() above but didn't, Solaris' version does).
   # the "b=xxx" string is also allocated on the heap.

After setenv("c", "xxx", 1)

envp: 0x7ffc2e7b3238 environ: 0x1bb3490
  environ[0]: 0x7ffc2e7b4ff0 (x=3)
  environ[1]: 0x1bb3440 (b=xxx)
  environ[2]: 0x1bb3420 (c=xxx)

Address of heap and stack:
01bb3000-01bd4000 rw-p 00000000 00:00                              [heap]
7ffc2e794000-7ffc2e7b5000 rw-p 00000000 00:00 0                    [stack]

No FreeBSD (11-rc1 aqui), uma nova lista já está alocada em unsetenv() . Não apenas isso, mas as próprias seqüências estão sendo copiadas para o heap, assim environ é completamente desconectado do envp[] que o programa recebeu na inicialização após a primeira modificação do ambiente:

envp: 0x7fffffffedd8 environ: 0x7fffffffedd8
  envp[0]: 0x7fffffffef74 (x=2)
  envp[1]: 0x7fffffffef78 (a=1)

After unsetenv("a")

envp: 0x7fffffffedd8 environ: 0x800e24000
  environ[0]: 0x800e15008 (x=2)

After setenv("b", "xxx", 1)

envp: 0x7fffffffedd8 environ: 0x800e24000
  environ[0]: 0x800e15018 (b=xxx)
  environ[1]: 0x800e15008 (x=2)

After setenv("c", "xxx", 1)

envp: 0x7fffffffedd8 environ: 0x800e24000
  environ[0]: 0x800e15020 (c=xxx)
  environ[1]: 0x800e15018 (b=xxx)
  environ[2]: 0x800e15008 (x=2)

No Solaris (11 aqui), vemos a otimização mencionada acima (onde unsetenv("a") acaba sendo feito com environ++ ), o slot liberado por unsetenv() sendo reutilizado por b , mas é claro que um Uma nova lista de ponteiros deve ser alocada após a inserção de uma nova variável de ambiente ( c ):

envp: 0xfeffef6c environ: 0xfeffef6c
  envp[0]: 0xfeffefec (a=1)
  envp[1]: 0xfeffeff0 (x=2)

After unsetenv("a")

envp: 0xfeffef6c environ: 0xfeffef70
  environ[0]: 0xfeffeff0 (x=2)

After setenv("b", "xxx", 1)

envp: 0xfeffef6c environ: 0xfeffef6c
  environ[0]: 0x806145c (b=xxx)
  environ[1]: 0xfeffeff0 (x=2)

After setenv("c", "xxx", 1)

envp: 0xfeffef6c environ: 0x8061c48
  environ[0]: 0x8061474 (c=xxx)
  environ[1]: 0x806145c (b=xxx)
  environ[2]: 0xfeffeff0 (x=2)
    
por 12.08.2016 / 13:08