PDA

View Full Version : String output for system()



voron00
30th December 2015, 17:19
Hi, i've changed the system() function to return a string of executed command instead of an builtin true/false. This allows to do some interesting stuff like executing external scripts in Perl/PHP etc. and get an output from them.
Grab it: https://github.com/voron00/libcod/commit/85fdd1bebcf3ca2ca271186715c667fb9868f82a
Here is a little example:
A geolocation script written in Perl

# List of modules
use strict; # strict keeps us from making stupid typos
use warnings; # helps us find problems in code
use diagnostics; # good for detailed explanations about any problems in code
use Geo::IP; # GeoIP is used for locating IP addresses

my $ip = $ARGV[0];

if (!$ip) { die("No IP-Address has been defined"); }

if ($ip =~ /^192\.168\.|^10\.|^169\.254\./) { die("Local Network"); }

if ($ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
die("Invalid IP-Address: $ip");
}

my $gi = Geo::IP->open("Geo/GeoLiteCity.dat", GEOIP_STANDARD);
my $record = $gi->record_by_addr($ip);
my $geo_ip_info;
if (!$record) { die("No location found for this IP-Address"); }

if ($record->city) {

# we know the city
if ($record->region_name) {

# and we know the region name
if ($record->city ne $record->region_name) {

# the city and region name are different, all three are relevant.
$geo_ip_info = $record->city . '^7,^2 ' . $record->region_name . ' ^7-^2 ' . $record->country_name;
}
else {
# the city and region name are the same. Use city and country.
$geo_ip_info = $record->city . '^7,^2 ' . $record->country_name;
}
}
else {
# Only two pieces we have are city and country.
$geo_ip_info = $record->city . '^7,^2 ' . $record->country_name;
}
}
elsif ($record->region_name) {

# don't know the city, but we know the region name and country. close enough.
$geo_ip_info = $record->region_name . '^7,^2 ' . $record->country_name;
}
elsif ($record->country_name) {

# We may not know much, but we know the country.
$geo_ip_info = $record->country_name;
}
elsif ($record->country_code3) {

# How about a 3 letter country code?
$geo_ip_info = $record->country_code3;
}
elsif ($record->country_code) {

# How about a 2 letter country code at least?
$geo_ip_info = $record->country_code;
}
else {
# I give up.
$geo_ip_info = "Unknown location";
}

print "$geo_ip_info\n";

Which i cruly copypasted from Nanny, this would return city (in some case not), region name, and country.
So in gsc i would run it like
location = system("perl locate.pl " + self getIP()); and then iprintln the result:
1008
Ye that's right.

voron00
30th December 2015, 21:24
Well shit, When running a more complicated code which output would be delayed, this would lag the server since it waits for that output, would need a thread or something...

kung foo man
31st December 2015, 00:36
inb4 async nodejs for libcod, or like izno did with async mysql

nice job and nice too see some ppl come up with something

maxdamage99
31st December 2015, 18:39
V KAKUYU DIRECTORIYU KIDAT' PERL SCRIPT??
p.s: ili perl iz lyboy "hvataet"??!

voron00
2nd January 2016, 06:44
V KAKUYU DIRECTORIYU KIDAT' PERL SCRIPT??
p.s: ili perl iz lyboy "hvataet"??!

You can run scripts from any directory by cd'ing && executing, but GeoIP database lookup takes too much time, it would lag the server as i said before.

Anyway, i tried to mess around the pthreads but there seems to be no way to push the value from a thread function into the gsc. Well, i reverted the original system() but added mine as execute():
https://github.com/voron00/libcod/commit/0e1130252542cabf7fea568d7e659bd93d081db8
Proably gonna leave it like that, seems fine by me.

maxdamage99
2nd January 2016, 11:30
вери бед инглиш ворон...
p.s:
if you got error:


Can't locate Geo/IP.pm in @INC (you may need to install the Geo::IP module) (@INC contains: /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .) at locate.pl line 5.
BEGIN failed--compilation aborted at locate.pl line 5 (#1)
(F) You said to do (or require, or use) a file that couldn't be found.
Perl looks for the file in all the locations mentioned in @INC, unless
the file name included the full path to the file. Perhaps you need
to set the PERL5LIB or PERL5OPT environment variable to say where the
extra library is, or maybe the script needs to add the library name
to @INC. Or maybe you just misspelled the name of the file. See
"require" in perlfunc and lib.

Uncaught exception from user code:
Can't locate Geo/IP.pm in @INC (you may need to install the Geo::IP module) (@INC contains: /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .) at locate.pl line 5.
BEGIN failed--compilation aborted at locate.pl line 5.

put in terminal:


perl -MCPAN -e "install Geo::IP::PurePerl"
perl -MCPAN -e "install Geo::IP"

or look: http://kb.odin.com/en/120247
http://dev.maxmind.com/geoip/legacy/install/city/

maxdamage99
2nd January 2016, 13:39
Data based:
https://dropmefiles.com/loZGb

IzNoGoD
2nd January 2016, 17:18
You forgot to free() the result you get from your function. Might cause out of memory errors if you use the function a lot.

maxdamage99
2nd January 2016, 17:34
How izno??

IzNoGoD
2nd January 2016, 20:11
How izno??

this is for voron

voron00
2nd January 2016, 22:17
this is for voron

Right yeah thx, actially its possible to do it w/o reallocating the memory and still getting the last line of the result: https://github.com/voron00/libcod/commit/a3f6f51c2beaa40aee81706f4a9b91bba6b580d0

kung foo man
3rd January 2016, 03:25
Just this:




void gsc_utils_execute() { // Returns complete command output as a string
char *cmd;
if ( ! stackGetParams("s", &cmd)) {
printf("scriptengine> ERROR: please specify the command as string to gsc_execute_command()\n");
stackPushUndefined();
return;
}
setenv("LD_PRELOAD", "", 1); // dont inherit lib of parent
char *result = exec(cmd);
stackPushString( result );
free(result); // <------
}



Because:

http://man7.org/linux/man-pages/man3/getline.3.html



getline() reads an entire line from stream, storing the address of
the buffer containing the text into *lineptr. The buffer is null-
terminated and includes the newline character, if one was found.

If *lineptr is set to NULL and *n is set 0 before the call, then
getline() will allocate a buffer for storing the line. This buffer
should be freed by the user program even if getline() failed.


And init the char *result in exec(char *) to NULL, since setting a pointer to NULL automatically is "luck" (depending on compiler if they init it).

Full test:



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

char *exec(const char* command) {
FILE *fp;
char *result = NULL; // plz init, otherwise it COULD be non-NULL (depending on compiler)
size_t len = 0;
fp = popen(command, "r");
if (fp == NULL) {
printf("Cannot execute command:\n%s\n", command);
return NULL;
}
while(getline(&result, &len, fp) != -1) {
fputs(result, stdout); // nice for debugging, but in actual release server kinda spammy to print every result
}
pclose(fp);
return result;
}

int main() {
char *result = exec("ls -l");
printf("ret: \"%s\"", result);
free(result);
return 0;
}


Just compile it:

gcc exec.c -o exec.exe

And run it with valgrind: valgrind --tool=memcheck ./exec.exe

Everything will be ok then, but when you forget free(result);, valgrind will print the error:



k_tracker@euve78301:~$ valgrind --tool=memcheck ./exec.exe
==17356== Memcheck, a memory error detector
==17356== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==17356== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==17356== Command: ./exec.exe
==17356==
total 625772
-rwxr-xr-x 1 k_tracker k_tracker 1300 Dez 12 2014 autobahn2.js
-rwxr-xr-x 1 k_tracker k_tracker 913 Dez 10 2014 autobahn.js
drwxr-xr-x 3 k_tracker k_tracker 4096 Okt 15 2013 bin
drwxrwxr-x 2 k_tracker k_tracker 4096 Nov 30 05:21 boost
-rwxr-xr-x 1 k_tracker k_tracker 22803 Jan 21 2015 calculator.js
-rwxr-xr-x 1 k_tracker k_tracker 3737 Dez 10 2014 chat.js
drwxr-xr-x 2 k_tracker k_tracker 4096 Okt 15 2013 classes
-rwxr-xr-x 1 k_tracker k_tracker 3475 Nov 19 2012 debug.php
-rwxr-xr-x 1 k_tracker k_tracker 330 Dez 15 2014 dns.js
-rw-rw-r-- 1 k_tracker k_tracker 516 Jan 3 03:15 exec.c
-rwxrwxr-x 1 k_tracker k_tracker 8832 Jan 3 03:15 exec.exe
drwxrwxr-x 2 k_tracker k_tracker 4096 Okt 1 03:06 filefront
-rw-rw-r-- 1 k_tracker k_tracker 452 Okt 1 03:00 kt.txt
drwxr-xr-x 4 k_tracker k_tracker 4096 Okt 15 2013 lib
-rwxr-xr-x 1 k_tracker k_tracker 3380 Nov 19 2012 main.php
drwxr-xr-x 14 k_tracker k_tracker 4096 Dez 1 20:16 mariadb
-rwxr-xr-x 1 k_tracker k_tracker 309533236 Jun 17 2015 mariadb.1
-rw-rw-r-- 1 k_tracker k_tracker 331091501 Okt 15 23:14 mariadb.tar.gz
-rwxr-xr-x 1 k_tracker k_tracker 3858 Jul 1 2015 my.cnf
drwxr-xr-x 7 k_tracker k_tracker 4096 Dez 10 2014 nodejs
drwxr-xr-x 28 k_tracker k_tracker 4096 Sep 24 16:07 node_modules
drwxr-xr-x 2 k_tracker k_tracker 4096 Jul 2 2015 ntop
-rwxr-xr-x 1 k_tracker k_tracker 6681 Nov 19 2012 query_master.js
-rwxr-xr-x 1 k_tracker k_tracker 335 Dez 10 2014 rpc.js
-rwxr-xr-x 1 k_tracker k_tracker 46 Okt 15 2013 server.sh
drwxr-xr-x 2 k_tracker k_tracker 4096 Sep 24 16:07 tmp
drwxr-xr-x 4 k_tracker k_tracker 4096 Jun 30 2015 tracker
drwxr-xr-x 4 k_tracker k_tracker 4096 Jun 30 2015 tracker_floater
drwxr-xr-x 5 k_tracker k_tracker 4096 Nov 15 00:13 tracker_iznogod
-rwxr-xr-x 1 k_tracker k_tracker 327 Jul 10 2014 unity2.js
-rwxr-xr-x 1 k_tracker k_tracker 1082 Jul 10 2014 unity.js
-rwxr-xr-x 1 k_tracker k_tracker 709 Dez 11 2014 wamp_client.js
-rwxr-xr-x 1 k_tracker k_tracker 238 Dez 11 2014 wamp.js
ret: "-rwxr-xr-x 1 k_tracker k_tracker 238 Dez 11 2014 wamp.js
"==17356==
==17356== HEAP SUMMARY:
==17356== in use at exit: 120 bytes in 1 blocks
==17356== total heap usage: 2 allocs, 1 frees, 376 bytes allocated
==17356==
==17356== LEAK SUMMARY:
==17356== definitely lost: 120 bytes in 1 blocks
==17356== indirectly lost: 0 bytes in 0 blocks
==17356== possibly lost: 0 bytes in 0 blocks
==17356== still reachable: 0 bytes in 0 blocks
==17356== suppressed: 0 bytes in 0 blocks
==17356== Rerun with --leak-check=full to see details of leaked memory
==17356==
==17356== For counts of detected and suppressed errors, rerun with: -v
==17356== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
k_tracker@euve78301:~$



About the pointer syntax, the asterix belongs to the variable name. You can simply understand this, when you want e.g. 2 pointers. What do you write?

This?


char* a, b;

Or this?


char *a, *b;

Well, as said already, the 2nd way is the correct way, since the asterix belongs to the variable name.

Also why just return the last line? Just return an array with every line :^)

Simple example from scandir():



void gsc_utils_scandir() {
char *dirname;
if ( ! stackGetParams("s", &dirname)) {
stackPushUndefined();
return;
}
DIR *dir;
struct dirent *dir_ent;
dir = opendir(dirname);
if ( ! dir) {
stackPushUndefined();
return;
}
stackPushArray();
while (dir_ent = readdir(dir)) {
stackPushString(dir_ent->d_name);
stackPushArrayLast();
}
closedir(dir);
}

maxdamage99
3rd January 2016, 09:32
Pth... What use me? :D