winix/core/app.cpp

1124 lines
24 KiB
C++
Executable File

/*
* This file is a part of Winix
* and is not publicly distributed
*
* Copyright (c) 2010-2011, Tomasz Sowa
* All rights reserved.
*
*/
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <sys/param.h>
#include <cstdio>
#include <fetch.h>
#include <signal.h>
#include "app.h"
#include "plugin.h"
#include "misc.h"
#include "functions/functions.h"
#include "utf8.h"
App::App()
{
stdout_is_closed = false;
last_sessions_save = std::time(0);
fcgi_socket = -1;
// temporary there is only one request
cur.request = &req;
cur.session = session_manager.GetTmpSession();
db.SetConn(db_conn);
plugin.SetDb(&db);
plugin.SetConfig(&config);
plugin.SetCur(&cur);
plugin.SetSystem(&system);
plugin.SetFunctions(&functions);
plugin.SetTemplates(&templates);
plugin.SetSynchro(&synchro);
plugin.SetSessionManager(&session_manager);
req.SetConfig(&config);
functions.SetConfig(&config);
functions.SetCur(&cur);
functions.SetDb(&db);
functions.SetSystem(&system);
functions.SetTemplates(&templates);
functions.SetSynchro(&synchro);
system.SetConfig(&config);
system.SetCur(&cur);
system.SetDb(&db);
system.SetSynchro(&synchro);
templates.SetConfig(&config);
templates.SetCur(&cur);
templates.SetDb(&db);
templates.SetSystem(&system);
templates.SetFunctions(&functions);
templates.SetSessionManager(&session_manager);
session_manager.SetLastContainer(&system.users.last);
session_manager.SetConfig(&config);
session_manager.SetCur(&cur);
session_manager.SetSystem(&system);
session_manager.SetSynchro(&synchro);
post_multi_parser.SetConfig(&config);
}
bool App::InitFCGI()
{
const char * sock = config.fcgi_socket.c_str();
unlink(sock);
fcgi_socket = FCGX_OpenSocket(sock, 100); // !! dodac 100 do konfiga
if( fcgi_socket < 0 )
{
log << log1 << "App: An error during creating a fcgi socket" << logend;
return false;
}
log << log3 << "App: FastCGI socket number: " << fcgi_socket << logend;
chmod(sock, config.fcgi_socket_chmod);
passwd * pw = getpwnam(config.fcgi_socket_user.c_str());
if( !pw )
{
log << log1 << "App: there is no user: " << config.fcgi_socket_user << logend;
return false;
}
group * gr = getgrnam(config.fcgi_socket_group.c_str());
if( !gr )
{
log << log1 << "App: there is no group: " << config.fcgi_socket_group << logend;
return false;
}
chown(sock, pw->pw_uid, gr->gr_gid);
if( FCGX_Init() != 0 )
{
log << log1 << "App: FCGX_Init fails" << logend;
return false;
}
if( FCGX_InitRequest(&fcgi_request, fcgi_socket, FCGI_FAIL_ACCEPT_ON_INTR) != 0 )
{
log << log1 << "App: FCGX_InitRequest fails" << logend;
return false;
}
return true;
}
bool App::Init()
{
db_conn.SetConnParam(config.db_database, config.db_user, config.db_pass);
db_conn.WaitForConnection();
db.LogQueries(config.log_db_query);
cur.request->Clear();
compress.Init();
system.Init();
functions.Init();
// init templates after functions are created
templates.CreateFunctions(); // create functions first (functions will be cached by patterns)
templates.ReadIndexFileNames();
templates.ReadTemplates();
// init notify after templates (it uses locales from templates)
system.notify.ReadTemplates();
session_manager.InitTmpSession();
session_manager.LoadSessions();
CreateStaticTree();
get_parser.UTF8(config.utf8);
post_parser.UTF8(config.utf8);
post_parser.LogValueSize(config.log_post_value_size);
// post_multi_parser has a pointer to the config
plugin.Call(WINIX_PLUGIN_INIT);
return true;
}
void App::Close()
{
session_manager.SaveSessions();
session_manager.DeleteSessions();
cur.request->Clear();
session_manager.UninitTmpSession();
}
bool App::BaseUrlRedirect()
{
if( config.base_url_http_host.empty() )
return false;
if( Equal(config.base_url_http_host.c_str(), cur.request->env_http_host) )
return false;
cur.request->redirect_to = config.base_url;
AssignString(cur.request->env_request_uri, cur.request->redirect_to, false);
log << log3 << "RC: BaseUrlRedirect from: " << cur.request->env_http_host << logend;
return true;
}
void App::ProcessRequestThrow()
{
ReadRequest();
// when BaseUrlRedirect() return true we didn't have to set everything in cur.request->Read()
// in the future cur.request->Read() can be split and at the beginning only environment variables will be read
// and then BaseUrlRedirect() will be called (for performance)
if( !BaseUrlRedirect() )
{
session_manager.SetSession();
cur.session = session_manager.GetCurSession();
if( cur.session->new_session )
{
cur.session->plugin_data.Resize(plugin.Size());
plugin.Call(WINIX_SESSION_CREATED);
}
plugin.Call(WINIX_SESSION_CHANGED);
functions.Parse();
system.mounts.CalcCurMount();
Make();
}
SendAnswer();
}
void App::ProcessRequest()
{
try
{
system.load_avg.StartRequest();
log << log2 << config.log_delimiter << logend;
ProcessRequestThrow();
SaveSessionsIfNeeded(); // !! przerzucic to na watek sesji
system.load_avg.StopRequest();
log << logendrequest;
}
catch(const std::exception & e)
{
log << log1 << "App: there was an exception: std::exception: " << e.what() << logend;
}
catch(const Error & e)
{
log << log1 << "App: there was an exception: Error: " << e << logend;
}
catch(...)
{
log << log1 << "App: there was an unknown exception" << logend;
}
try
{
plugin.Call(WINIX_END_REQUEST);
}
catch(...)
{
log << log1 << "App: an exception when clearing the request (from a plugin)" << logend;
}
cur.request->Clear();
cur.session = session_manager.GetTmpSession();
}
void App::Start()
{
while( !synchro.was_stop_signal && FCGX_Accept_r(&fcgi_request) == 0 )
{
Lock();
if( !synchro.was_stop_signal )
ProcessRequest();
Unlock();
}
}
void App::SaveSessionsIfNeeded()
{
time_t t = std::time(0);
if( last_sessions_save + 86400 > t )
return;
// saving once a day for safety
last_sessions_save = t;
session_manager.SaveSessions();
}
// !! zmienic na lepsza nazwe
void App::MakePage()
{
bool sent = false;
if( !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
return;
if( cur.request->is_item && cur.request->item.file_type == WINIX_ITEM_FILETYPE_NONE &&
cur.request->item.content_type == Item::ct_raw && cur.request->status == WINIX_ERR_OK && cur.request->function )
{
if( cur.request->function == &functions.fun_cat )
{
cur.request->page << cur.request->item.content;
sent = true;
}
else
if( cur.request->function == &functions.fun_run )
{
templates.GenerateRunRaw();
sent = true;
}
}
if( !sent )
{
templates.Generate();
}
}
// zmienic nazwe np na ProcessRequest
// !! ta nazwa chyba juz zajeta...
void App::Make()
{
if( cur.request->dir_tab.empty() )
{
log << log1 << "Content: there is no a root dir (dir_tab is empty)" << logend;
return;
}
// cur.request->status can be changed by function_parser
if( cur.request->status == WINIX_ERR_OK )
plugin.Call(WINIX_PREPARE_REQUEST);
if( cur.request->status == WINIX_ERR_OK )
functions.CheckFunctionAndSymlink();
if( cur.request->status == WINIX_ERR_OK )
functions.MakeFunction();
if( cur.session->spam_score > 0 )
log << log1 << "App: spam score: " << cur.session->spam_score << logend;
if( cur.request->IsParam(L"noredirect") )
cur.request->redirect_to.clear();
if( cur.request->status == WINIX_ERR_OK )
plugin.Call(WINIX_PROCESS_REQUEST);
if( !cur.request->redirect_to.empty() )
return;
if( cur.request->dir_tab.empty() )
{
log << log1 << "App: there is no a root dir (dir_tab is empty -- after calling a function)" << logend;
return;
}
plugin.Call(WINIX_CONTENT_MAKE);
MakePage();
if( config.debug_info )
{
// !! dodac inne informacje (get, post, itp)
// jesli jest debug_info wlaczone to nie robic przekierowan
cur.request->PrintGetTab();
//cur.request->PrintEnv(); // !! PrintEnv() mozna przeniesc tutaj (do klasy App)
}
}
void App::ReadRequest()
{
ReadEnvVariables();
CheckRequestMethod();
CheckFCGIRole();
LogAccess();
ReadGetPostVars();
cookie_parser.Parse(cur.request->env_http_cookie, cur.request->cookie_tab);
accept_encoding_parser.ParseAndLog(cur.request->env_http_accept_encoding);
CheckIE();
CheckKonqueror();
if( cur.request->role == Request::authorizer )
log << log3 << "Request: fast cgi role: authorizer" << logend;
}
void App::SetEnv(const char * & env, const char * name)
{
const char * v = FCGX_GetParam(name, fcgi_request.envp);
if( v )
env = v;
// by default env is set to an empty string (in cur.request->Clear() method)
}
void App::ReadEnvVariables()
{
// we store that values because FCGX_GetParam has O(n) complexity
// with this variables (env_*) we have O(1)
SetEnv(cur.request->env_request_method, "REQUEST_METHOD"); // !! mozna nie uzywac tego, teraz mamy w strukturze fcgi_request
SetEnv(cur.request->env_request_uri, "REQUEST_URI");
SetEnv(cur.request->env_http_cookie, "HTTP_COOKIE");
SetEnv(cur.request->env_remote_addr, "REMOTE_ADDR");
SetEnv(cur.request->env_http_host, "HTTP_HOST");
SetEnv(cur.request->env_http_user_agent, "HTTP_USER_AGENT");
SetEnv(cur.request->env_fcgi_role, "FCGI_ROLE");
SetEnv(cur.request->env_content_type, "CONTENT_TYPE");
SetEnv(cur.request->env_http_accept_encoding,"HTTP_ACCEPT_ENCODING");
}
void App::CheckRequestMethod()
{
cur.request->method = Request::none;
if( ToSmall(cur.request->env_request_method[0]) == 'g' )
cur.request->method = Request::get;
else
if( ToSmall(cur.request->env_request_method[0]) == 'p' )
cur.request->method = Request::post;
else
if( ToSmall(cur.request->env_request_method[0]) == 'h' )
cur.request->method = Request::head;
}
void App::CheckFCGIRole()
{
// default we assume 'responder'
cur.request->role = Request::responder;
if( ToSmall(cur.request->env_fcgi_role[0]) == 'a' )
cur.request->role = Request::authorizer;
}
void App::LogAccess()
{
log.PutDate(log1);
log << cur.request->env_remote_addr << ' '
<< cur.request->env_request_method << ' '
<< cur.request->env_http_host
<< cur.request->env_request_uri << ' '
<< cur.request->env_http_user_agent << logend;
}
void App::ReadGetPostVars()
{
// get parameters we have always
get_parser.Parse(cur.request->env_request_uri, cur.request->get_tab);
if( cur.request->method == Request::post )
{
if( IsSubStringNoCase("multipart/form-data", cur.request->env_content_type) )
{
log << log3 << "Request: post content type: multipart/form-data" << logend;
post_multi_parser.Parse(fcgi_request.in, cur.request->post_tab, cur.request->post_file_tab);
}
else
{
post_parser.Parse(fcgi_request.in, cur.request->post_tab);
}
}
}
void App::CheckIE()
{
char * msie = strstr(cur.request->env_http_user_agent, "MSIE");
if( msie )
cur.request->browser_msie = true;
else
cur.request->browser_msie = false;
}
void App::CheckKonqueror()
{
char * kon = strstr(cur.request->env_http_user_agent, "Konqueror");
if( kon )
cur.request->browser_konqueror = true;
else
cur.request->browser_konqueror = false;
}
void App::PrepareSessionCookie()
{
if( !cur.session || cur.session->id==0 )
return;
if( !cur.session->puser || !cur.session->remember_me )
{
cur.request->SetCookie(config.http_session_id_name.c_str(), cur.session->id);
}
else
{
time_t t = std::time(0) + config.session_remember_max_idle;
tm expires = Time(t);
cur.request->SetCookie(config.http_session_id_name.c_str(), cur.session->id, &expires);
}
}
void App::SendHeaders(bool compressing, int compress_encoding, Header header)
{
PrepareSessionCookie();
if( cur.request->send_as_attachment )
FCGX_PutS("Content-Disposition: attachment\r\n", fcgi_request.out);
if( !cur.request->redirect_to.empty() )
{
FCGX_PutS("Status: 301 Moved Permanently\r\n", fcgi_request.out);
UrlEncode(cur.request->redirect_to, cur.request->aredirect_to);
FCGX_FPrintF(fcgi_request.out, "Location: %s\r\n", cur.request->aredirect_to.c_str());
log << log2 << "Redirect to: " << cur.request->aredirect_to << logend;
}
else
if( !cur.request->x_sendfile.empty() )
{
static std::string temp, temp2; // !! wrzucic gdzies to
Ezc::WideToUTF8(config.http_header_send_file, temp);
Ezc::WideToUTF8(cur.request->x_sendfile, temp2);
FCGX_FPrintF(fcgi_request.out, "%s: %s\r\n", temp.c_str(), temp2.c_str());
FCGX_PutS("Status: 200 OK\r\n", fcgi_request.out);
log << log2 << "Sending file: " << cur.request->x_sendfile << logend;
}
else
{
switch( header )
{
case h_404:
FCGX_PutS("Status: 404 Not Found\r\n", fcgi_request.out);
FCGX_PutS("Content-Type: text/html\r\n", fcgi_request.out);
log << log2 << "Request: response: 404 Not Found" << logend;
break;
case h_403:
FCGX_PutS("Status: 403 Forbidden\r\n", fcgi_request.out);
FCGX_PutS("Content-Type: text/html\r\n", fcgi_request.out);
log << log2 << "Request: response: 403 Forbidden" << logend;
break;
default:
FCGX_PutS("Status: 200 OK\r\n", fcgi_request.out);
if( cur.request->role != Request::authorizer )
FCGX_PutS("Content-Type: text/html\r\n", fcgi_request.out);
}
}
if( compressing )
{
if( compress_encoding == 0 || compress_encoding == 1 )
FCGX_PutS("Content-Encoding: deflate\r\n", fcgi_request.out);
else
FCGX_PutS("Content-Encoding: gzip\r\n", fcgi_request.out);
}
FCGX_PutS(cur.request->headers.CStr(), fcgi_request.out);
FCGX_PutS("\r\n", fcgi_request.out);
}
void App::FilterCompressSend(bool compressing, int compress_encoding, const std::wstring & source_ref)
{
const std::wstring * source = &source_ref;
bool raw = cur.request->is_item && cur.request->item.content_type == Item::ct_raw && cur.request->status == WINIX_ERR_OK &&
cur.request->function && (cur.request->function == &functions.fun_cat || cur.request->function == &functions.fun_run);
if( config.html_filter && !raw )
{
TemplatesFunctions::html_filter.Filter(*source, clean_html);
AddDebugInfo(clean_html);
source = &clean_html;
}
else
{
html_with_debug = *source;
AddDebugInfo(html_with_debug);
source = &html_with_debug;
}
if( config.utf8 )
Ezc::WideToUTF8(*source, source_a);
else
AssignString(*source, source_a);
if( compressing )
compress.CompressAndPut(source_a.c_str(), source_a.length(), fcgi_request.out, compress_encoding);
else
FCGX_PutS(source_a.c_str(), fcgi_request.out);
}
int App::SelectDeflateVersion()
{
if( cur.request->browser_msie )
return 0; // raw deflate
else
return 1; // deflate
}
void App::SelectCompression(size_t source_len, bool & compression_allowed, int & compression_encoding)
{
compression_allowed = false;
compression_encoding = 0;
if( config.compression &&
cur.request->role == Request::responder &&
cur.request->redirect_to.empty() &&
cur.request->x_sendfile.empty() &&
!cur.request->browser_konqueror && /* !! sprawdzic czy Konqueror bedzie obslugiwal raw deflate */
source_len >= config.compression_page_min_size )
{
if( config.compression_encoding == 1 || config.compression_encoding == 10 )
{
if( accept_encoding_parser.AcceptDeflate() )
{
compression_allowed = true;
compression_encoding = SelectDeflateVersion();
}
else
if( config.compression_encoding == 10 && accept_encoding_parser.AcceptGzip() )
{
compression_allowed = true;
compression_encoding = 2; // gzip
}
}
if( config.compression_encoding == 2 || config.compression_encoding == 20 )
{
if( accept_encoding_parser.AcceptGzip() )
{
compression_allowed = true;
compression_encoding = 2; // gzip
}
else
if( config.compression_encoding == 20 && accept_encoding_parser.AcceptDeflate() )
{
compression_allowed = true;
compression_encoding = SelectDeflateVersion();
}
}
}
}
bool App::CanSendContent(Header header)
{
if( !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
// if there is a redirect or a file to send then we do not send a content
return false;
if( header == h_200 && cur.request->role == Request::authorizer && cur.request->is_item && cur.request->item.file_type != WINIX_ITEM_FILETYPE_NONE )
// if there is an item and the item has 'file' storage we do not send a content
return false;
/*
we don't have to check the HEAD method
the server (lighttpd) doesn't send the body of its own
*/
if( cur.request->method == Request::head )
return false;
return true;
}
void App::AddDebugInfo(std::wstring & out)
{
if( config.debug_info )
{
if( !cur.request->debug.Empty() )
{
out += L"\n<!--\n";
out += cur.request->debug.Str();
out += L"\n-->\n";
}
}
}
void App::SendAnswer()
{
const std::wstring & source = cur.request->page.Str();
Header header = h_200;
Error status = cur.request->status;
bool compressing;
int compress_encoding;
SelectCompression(source.length(), compressing, compress_encoding);
if( status == WINIX_ERR_NO_ITEM || status == WINIX_ERR_NO_FUNCTION || status == WINIX_ERR_UNKNOWN_PARAM )
header = h_404;
if( status == WINIX_ERR_PERMISSION_DENIED || status == WINIX_ERR_CANT_CHANGE_USER || status == WINIX_ERR_CANT_CHANGE_GROUP )
header = h_403;
SendHeaders(compressing, compress_encoding, header);
if( CanSendContent(header) )
{
// filtering (html), compressing and sending back to the web browser
FilterCompressSend(compressing, compress_encoding, source);
}
}
void App::LogUser(const char * msg, uid_t id)
{
log << log3 << msg << " ";
passwd * p = getpwuid(id);
if( p )
log << p->pw_name;
else
log << (int)id;
log << logend;
}
void App::LogGroup(const char * msg, gid_t id, bool put_logend)
{
log << log3 << msg << " ";
group * g = getgrgid(id);
if( g )
log << g->gr_name;
else
log << (int)id;
if( put_logend )
log << logend;
}
void App::LogUsers()
{
uid_t eid, rid;
eid = geteuid();
rid = getuid();
if( eid == rid )
{
LogUser("App: effective/real user:", eid);
}
else
{
LogUser("App: effective user:", eid);
LogUser("App: real user:", rid);
}
}
void App::LogEffectiveGroups(std::vector<gid_t> & tab)
{
log << log3 << "App: effective groups:";
for(size_t i=0 ; i<tab.size() ; ++i)
{
bool was_printed = false;
for(size_t x=0 ; x<i ; ++x)
{
if( tab[i] == tab[x] )
{
was_printed = true;
break;
}
}
if( !was_printed )
LogGroup("", tab[i], false);
}
log << logend;
}
void App::LogGroups()
{
std::vector<gid_t> tab;
gid_t rgid;
int len;
rgid = getgid();
len = getgroups(0, 0);
if( len <= 0 )
{
log << log3 << "App: I can't read how many groups there are" << logend;
return;
}
tab.resize(len);
len = getgroups(len, &(tab[0]));
if( len == -1 )
{
log << log3 << "App: I can't read groups" << logend;
return;
}
if( len == 1 && rgid == tab[0] )
{
LogGroup("App: effective/real group:", rgid);
}
else
{
tab.resize(len);
LogEffectiveGroups(tab);
LogGroup("App: real group:", rgid);
}
}
void App::LogUserGroups()
{
LogUsers();
LogGroups();
}
bool App::DropPrivileges(const std::string & user, uid_t uid, gid_t gid, bool additional_groups)
{
if( additional_groups )
{
if( initgroups(user.c_str(), gid) < 0 )
{
log << log1 << "App: I can't init groups for user: " << user << logend;
return false;
}
}
else
{
if( setgroups(1, &gid) < 0 )
{
log << log1 << "App: I can't init groups for user: " << user << logend;
return false;
}
}
// for setting real and saved gid too
if( setgid(gid) )
{
log << log1 << "App: I can't change real and saved gid" << logend;
return false;
}
if( setuid(uid) < 0 )
{
log << log1 << "App: I can't drop privileges to user: " << user
<< " (uid:" << (int)uid << ")" << logend;
return false;
}
if( getuid()==0 || geteuid()==0 )
{
log << log1 << "App: sorry, for security reasons you should not run me as the root" << logend;
return false;
}
return true;
}
bool App::DropPrivileges()
{
if( getuid()!=0 && geteuid()!=0 )
return true;
log << log2 << "App: dropping privileges" << logend;
if( config.user.empty() )
{
log << log1 << "App: you should specify user name in the config file "
<< "to which I have to drop privileges" << logend;
return false;
}
if( config.group.empty() )
{
log << log1 << "App: you should specify group name in the config file "
<< "to which I have to drop privileges" << logend;
return false;
}
passwd * p = getpwnam(config.user.c_str());
group * g = getgrnam(config.group.c_str());
if( !p )
{
log << log1 << "App: there is no such a user as: \"" << config.user << "\"" << logend;
return false;
}
if( !g )
{
log << log1 << "App: there is no such a group as: \"" << config.group << "\"" << logend;
return false;
}
if( !DropPrivileges(config.user, p->pw_uid, g->gr_gid, config.additional_groups) )
return false;
return true;
}
bool App::Demonize()
{
// in linux fork() should be used twice
int pid = fork();
if( pid == -1 )
{
log << log1 << "App: I can't demonize myself (fork problem)" << logend;
return false;
}
if( pid != 0 )
{
// parent
exit(0);
}
// child
if( setsid() == -1 )
{
log << log1 << "App: I can't demonize myself (setsid problem)" << logend;
return false;
}
return true;
}
// this method is called from a signal routine
void App::SetStopSignal()
{
synchro.was_stop_signal = true;
}
bool App::WasStopSignal()
{
return synchro.was_stop_signal;
}
bool App::Lock()
{
return synchro.Lock();
}
void App::Unlock()
{
synchro.Unlock();
}
void App::WaitForThreads()
{
// special thread hangs on fetchStatURL -- I don't know why
// but it doesn't matter, don't use pthread_join on it
//pthread_join(signal_thread, 0);
system.notify.WaitForThread();
session_manager.WaitForThread();
system.thumb.WaitForThread();
}
void App::FetchPageOnExit()
{
// stupid trick to break FCGX_Accept_r() function
// even with FCGX_InitRequest(..., ..., FCGI_FAIL_ACCEPT_ON_INTR) the FCGX_Accept_r
// doesn't want to break on a signal
// so we request one page from the server for exiting from FCGX_Accept_r
url_stat url;
fetchStatURL(url_to_fetch_on_exit.c_str(), &url, "");
}
void * App::SpecialThreadForSignals(void * app_object)
{
sigset_t set;
App * app = reinterpret_cast<App*>(app_object);
sigemptyset(&set);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGINT);
// waiting for SIGTERM or SIGINT
sigwait(&set, 0);
app->Lock();
app->synchro.was_stop_signal = true;
FCGX_ShutdownPending();
Ezc::WideToUTF8(app->config.base_url, app->url_to_fetch_on_exit);
app->system.notify.PrepareToStopThread();
app->session_manager.WakeUpThread();
app->system.thumb.WakeUpThread();
app->Unlock();
// this thread will hang on this method
// but will be terminated when the main thread exits
app->FetchPageOnExit();
pthread_exit(0);
return 0;
}
void App::StartThreads()
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGINT);
// blocking SIGTERM and SIGINT
// new threads will have the signals blocked too
pthread_sigmask(SIG_BLOCK, &set, 0);
// special thread only for signals
pthread_create(&signal_thread, 0, SpecialThreadForSignals, this);
// thread for notifications
system.notify.StartThread();
// gc for sessions
session_manager.StartThread();
// thumbnails
system.thumb.StartThread();
}
void App::CreateStaticTree()
{
if( config.upload_dir.empty() )
{
log << log1 << "App: config: upload_dir not set, you are not allowed to upload static content" << logend;
return;
}
CreateDirs(L"/", config.upload_dir.c_str(), config.upload_dirs_chmod);
CreateDirs(config.upload_dir.c_str(), L"simplefs/normal", config.upload_dirs_chmod);
CreateDirs(config.upload_dir.c_str(), L"simplefs/thumb", config.upload_dirs_chmod);
CreateDirs(config.upload_dir.c_str(), L"hashfs/normal", config.upload_dirs_chmod);
CreateDirs(config.upload_dir.c_str(), L"hashfs/thumb", config.upload_dirs_chmod);
CreateDirs(config.upload_dir.c_str(), L"tmp", config.upload_dirs_chmod);
}