Calling C function with array pointer and int pointer from Swift - arrays

I am creating a C lib plus a wrapper for easy use in Swift. The C function takes two parameters, an array pointer and an int pointer:
int crgetproclist(struct kinfo_proc *proc_list, size_t *count) {
int err = 0;
size_t length = 0;
static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
// Call sysctl with a NULL buffer to get proper length
err = sysctl((int *)name, (sizeof(name) / sizeof(*name)) - 1, NULL, &length, NULL, 0);
//if (err) return [-1];
// Get the actual process list
err = sysctl((int *)name, (sizeof(name) / sizeof(*name)) - 1, proc_list, &length, NULL, 0);
//if (err) return [-1];
*count = length / sizeof(struct kinfo_proc);
for (int i = 0; i < *count; i++) {
struct kinfo_proc proc = proc_list[i];
proc = proc;
}
return 1;
}
I call that function from my Swift wrapper:
var data: [Process] = []
override func viewDidLoad() {
super.viewDidLoad()
let proc_list: UnsafeMutablePointer<kinfo_proc> = UnsafeMutablePointer<kinfo_proc>.allocate(capacity: 500)
var count: size_t = 0
let result = crgetproclist(proc_list, &count)
var foobar: [Process] = []
if (result == 1) {
for i in 1..<count {
var proc: kinfo_proc = proc_list[i]
var process = Process(proc: proc)
foobar.append(process) // <---- works
self.data.append(process) // <---- EXC_BAD_ACCESS ????
}
self.data.sort(by: {
(a: Process, b: Process) -> Bool in
return a.name.lowercased() < b.name.lowercased()
})
self.myTable.reloadData()
}
}
class Process: NSObject {
var _proc: kinfo_proc
var pid: pid_t
var name: String
var icon: NSImage?
var isAlive: Bool = false
var uid: uid_t = 0
init(proc: kinfo_proc) {
self._proc = proc
self.pid = proc.kp_proc.p_pid
self.name = String(cString: crgetprocname(pid))
self.uid = crgetuid(pid)
super.init()
}
}
Questions
How to correctly create and pass an UnsafeMutablePointer to the C function? I hard coded capacity: 500 which works, but how to do it correctly without a hardcoded capacity?
When I try to append it to my class variable array data it runs into a EXC_BAD_ACCESS, but when I append it to foobar which is the same type it works. Why? How to assign it to a class variable without memory error?

I can only answer the first part of your question: In order to determine the necessary allocation count for the process list you must allow that crgetproclist() is called with a NULL argument (in the same way as sysctl() can be called with a NULL argument for oldp to get the needed buffer size):
int crgetproclist(struct kinfo_proc *proc_list, size_t *count) {
int err = 0;
size_t length;
static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
if (proc_list == NULL) {
// Call sysctl with a NULL buffer to get proper length
length = 0;
err = sysctl((int *)name, (sizeof(name) / sizeof(*name)), NULL, &length, NULL, 0);
} else {
// Get the actual process list
length = *count * sizeof(struct kinfo_proc);
err = sysctl((int *)name, (sizeof(name) / sizeof(*name)), proc_list, &length, NULL, 0);
}
if (err) return -1;
*count = length / sizeof(struct kinfo_proc);
return 1;
}
Now you can call that function from Swift twice: First to determine the allocation count, and then again to retrieve the process list:
var count: size_t = 0
crgetproclist(nil, &count)
let procList = UnsafeMutablePointer<kinfo_proc>.allocate(capacity: count)
if crgetproclist(procList, &count) == 1 {
for i in 0..<count {
let proc = procList[i]
// ...
}
}
procList.deallocate()
Note also that you can implement the function easily in pure Swift:
func getProcessList() -> [kinfo_proc]? {
var name : [Int32] = [ CTL_KERN, KERN_PROC, KERN_PROC_ALL ]
var length = size_t()
sysctl(&name, UInt32(name.count), nil, &length, nil, 0)
let count = length / MemoryLayout<kinfo_proc>.size
var procList = Array(repeating: kinfo_proc(), count: count)
let result = sysctl(&name, UInt32(name.count), &procList, &length, nil, 0)
guard result == 0 else { return nil } // Some error ...
return Array(procList.prefix(length / MemoryLayout<kinfo_proc>.size))
}

Related

Why is config_read_file() returning config_false?

I have been using libconfig for config files in a project. When I remove the double quotes from sources by source_to_use, config_read_file() returns config_true and also has a syntax error. The syntax error will cause my getter for the source_to_use option to go to the default case. Also because of this my getter for the source array, will also go to the else case. Could this just be me making a simple syntax error with the libconfig format?
This is the config file I am using:
#config for walld
#colors
colors = TRUE;
source_to_use: "sources";
default:
[
"/home/seth/Pictures/kimi.png"
];
sources:
[
"/home/seth/.walld/persona",
"/home/seth/.walld/image-urls"
];
This is the function I have reading it:
settings* read_config(const char* config_file, const char* home_dir) {
settings* options = malloc(sizeof(settings));
config_t config;
config_setting_t* setting;
const char* source;
int colors;
config_init(&config);
if (config_read_file(&config, config_file) == CONFIG_TRUE) {
config_destroy(&config);
return NULL;
}
if (config_lookup_bool(&config, "colors", &colors)) {
options->colors = colors;
}
else {
options->colors = 0;
}
if (config_lookup_string(&config, "source_to_use", &source)) {
//NOP
}
else {
source = "default";
}
setting = config_lookup(&config, source);
if (setting != NULL) {
int count = config_setting_length(setting);
linked_node* entry_point = add_node_to_list(NULL, NULL);
linked_node* current = entry_point;
options->sources = entry_point;
for (int i = 0; i < count; i++) {
char* item = config_setting_get_string_elem(setting, i);
current = add_node_to_list(current, item);
}
}
else {
options->sources = malloc(sizeof(linked_node));
int char_count = snprintf(NULL, 0, "%s%s", home_dir, "/.walld/images");
if (char_count <= 0) {
//tough luck
abort();
}
char* default_folder = malloc(char_count + 1U);
if (default_folder == NULL) {
//tough luck
abort();
}
snprintf(default_folder, char_count + 1U, "%s%s", home_dir, "/.walld/images");
options->sources->image = default_folder;
}
config_destroy(&config);
return options;
}
In your read_config function, your first if is:
if (config_read_file(&config, config_file) == CONFIG_TRUE) {
config_destroy(&config);
return NULL;
}
The sense of the if is reversed, so you'll return a NULL if the read of the file is valid.
So, you want to reverse the sense of this if:
if (config_read_file(&config, config_file) != CONFIG_TRUE) {
config_destroy(&config);
return NULL;
}
Or you could [probably] use:
if (config_read_file(&config, config_file) == CONFIG_FALSE) {

C & libconfig: config_lookup_bool returns CONFIG_FALSE

I'm trying to read a bool setting from a group. I'm using the following code, but config_lookup_bool always returns CONFIG_FALSE. As far as I understood it should write the value into send_keys and return CONFIG_TRUE instead.
Code:
int send_keys;
config_t cfg;
config_init(&cfg);
config_read_file(&cfg, "config.cfg")
if (config_lookup_bool(&cfg, "settings.send_keys", &send_keys))
{
// do something here
}
config.cfg:
settings :
{
send_keys = "true";
start_apps = "false";
sync_clocks = "false";
pc_clock_is_origin = "true";
calibration_start_time = 0L;
};
Is there any mistake in my code or my thoughts?
There is a config_lookup_bool example here that includes the config file and the code: (use this example to compare against what you have.)
config file contents:
# authenticator
name = "JP";
enabled = false;
length = 186;
ldap = {
host = "ldap.example.com";
base = "ou=usr,o=example.com"; /* adapt this */
retries = [10, 15, 20, 60]; // Use more than 2
};
Source to read and process it...
int main(int argc, char **argv)
{
config_t cfg, *cf;
const config_setting_t *retries;
const char *base = NULL;
int count, n, enabled;
cf = &cfg;
config_init(cf);
if (!config_read_file(cf, "ldap.cfg")) {
fprintf(stderr, "%s:%d - %s\n",
config_error_file(cf),
config_error_line(cf),
config_error_text(cf));
config_destroy(cf);
return(EXIT_FAILURE);
}
if (config_lookup_bool(cf, "enabled", &enabled))
printf("Enabled: %s\n", enabled ? "Yep" : "Nope");
else
printf("Enabled is not defined\n");
if (config_lookup_string(cf, "ldap.base", &base))
printf("Host: %s\n", base);
retries = config_lookup(cf, "ldap.retries");
count = config_setting_length(retries);
printf("I have %d retries:\n", count);
for (n = 0; n < count; n++) {
printf("\t#%d. %d\n", n + 1,
config_setting_get_int_elem(retries, n));
}
config_destroy(cf);
return 0;
}
Thanks for your input. The problem was that I had true/false in "" and therefore it was parsed as string. It should have been
settings :
{
send_keys = true;
start_apps = false;
sync_clocks = false;
pc_clock_is_origin = true;
calibration_start_time = 0L;
};

How to include the size of array into the for loop without triggering error

This is the login function that I want to implement.
The problem is that I want to use the syntax of sizeof(id) in the for loop without triggering error.
Any solution??
int login();
int login()
{
int i, att, num_i, status;
att = 1;
status = 0;
num_i = 999;
char* id[100], * pass[100];
char* inp_id[100], inp_pass[100];
id[0] = "id1"; ///Sample ID
id[1] = "id2";
id[2] = "id3";
pass[0] = "pass1"; ///Sample pass
pass[1] = "pass2";
pass[2] = "pass3";
while (att <= 3)
{
printf("ID:");
scanf("%s", &inp_id);
for (i = 0; i < 3; ++i) /// I wanted this to repeat accordingly to the size of ID that was stored
{
if (strcmp(inp_id, id[i]) == 0) /// Cuz when I declare i > 100 when it call for i[4] and it doesn't exist, error occured.
{
num_i = i;
break; /// wanted it to break out of the loop once it got the id that's similar to what was entered
}
}
printf("Password:");
scanf("%s", &inp_pass);
if (num_i < 100)
{
if (strcmp(inp_pass, pass[num_i]) == 0)///checking pass according to the positon of i on the ID
{
status = 1;
att = 999;
}
}
att++;
}
I've deleted a portion of the code due to it asking for more information.
A simplified example of what I described in my comment:
Or at least these are components to help you understand.
struct account {
char *id;
char *pass;
};
static const struct account account_list[] = {
{ .id = "id1", .pass = "pass1" },
{ .id = "id2", .pass = "pass2" },
{ .id = "id3", .pass = "pass3" },
{ NULL },
};
struct account *a;
for (a = account_list; a.id; a++) {
....
}
Something like this is much easier to work with.

Programmatically change VersionInfo of a foreign DLL

I am trying to programmatically change the VersionInfo attributes of a DLL file. I used this article as reference.
#include <iostream>
#include <stdio.h>
#include <windows.h>
int main(int argc, char** argv) {
LPCTSTR lpszFile = "E:\\_test\\rand_test\\test.dll";
DWORD dwHandle,
dwSize;
struct {
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
// determine the size of the resource information
dwSize = GetFileVersionInfoSize(lpszFile, &dwHandle);
if (0 < dwSize)
{
unsigned char* lpBuffer = (unsigned char*) malloc(dwSize);
// Get whole VersionInfo resource/structure
GetFileVersionInfo(lpszFile, 0, dwSize, lpBuffer);
char strSubBlock[37]; // fits "\\StringFileInfo\\xxxxxxxx\\CompanyName\0"
LPTSTR pValueBuffer;
HANDLE hResource = BeginUpdateResource(lpszFile, FALSE);
if (NULL != hResource)
{
UINT uTemp;
// get the language information
if (!VerQueryValue(lpBuffer, "\\VarFileInfo\\Translation", (LPVOID *) &lpTranslate, &uTemp) != FALSE)
{
printf("Error 1\n");
return 1;
}
sprintf(strSubBlock, "\\StringFileInfo\\%04x%04x\\CompanyName", lpTranslate->wLanguage, lpTranslate->wCodePage);
if (!VerQueryValue(lpBuffer, (LPTSTR) ((LPCTSTR) strSubBlock), (LPVOID *) &pValueBuffer, &uTemp)) {
printf("Error 2\n");
return 1;
}
// PROBLEM!!!
// (pValueBuffer-lpBuffer) is 0x438 (longer than the Versioninfo resource!) but should be 0xB8
// so, pValueBuffer does not point to the actual company name.
ZeroMemory(pValueBuffer, strlen(pValueBuffer) * sizeof(TCHAR));
strcpy(pValueBuffer, "My Company, Inc."); // String may only become smaller or equal, never bigger than strlen(pValueBuffer)
if (UpdateResource(hResource,
RT_VERSION,
MAKEINTRESOURCE(VS_VERSION_INFO),
lpTranslate->wLanguage, // or 0
lpBuffer,
dwSize) != FALSE)
{
EndUpdateResource(hResource, FALSE);
}
}
free(lpBuffer);
}
return 0;
}
I think I have understood everything the code does. The plan is to read the Versioninfo block, then find the position where e.g. the CompanyName lies using VerQueryValue, then change the data, and then write it back using UpdateResource.
But there is a problem: VerQueryValue should output the position where the CompanyName string lies. But instead, it gives a pointer location, which is a few hundred bytes away, so it points somewhere outside the VersionInfo structure.
What am I doing wrong and how can I make it work?
(Also, does anybody know if there is a more elegant way to do this task, maybe even remove the limitation that the string has to be smaller or equal to the original?)
version resource this is serialized tree. if you want modify it - you need deserialize it to tree structure in memory, modify node, and serialize to new memory.
despite in msdn defined several Version Information Structures, really all it have common format
struct RsrcHeader
{
WORD wLength;
WORD wValueLength;
WORD wType;
WCHAR szKey[];
// aligned on 4*n
// BYTE Value[wValueLength]; // if (wType == 0)
// or
// WCHAR Value[wValueLength]; // if (wType == 1)
// every element aligned on 4*n
// RsrcHeader childs[];
};
so possible write common parse and serialize procedures
#if DBG
#define DBG_OPT(x) _CRT_UNPARENTHESIZE(x)
#else
#define DBG_OPT(x)
#endif
class RsrcNode
{
struct RsrcHeader
{
WORD wLength;
WORD wValueLength;
WORD wType;
WCHAR szKey[];
};
C_ASSERT(sizeof(RsrcHeader) == 6);
RsrcNode* _first, *_next;
PCWSTR _name;
const void* _pvValue;
ULONG _cbValue;
WORD _wValueLength;
WORD _wType;
DBG_OPT((PCSTR _prefix)); // only for debug output
public:
bool ParseResourse(PVOID buf, ULONG size, ULONG* pLength, PCSTR prefix);
RsrcNode(DBG_OPT((PCSTR prefix = "")))
: _next(0), _first(0) DBG_OPT((, _prefix(prefix)))
{
}
~RsrcNode();
bool IsStringValue() const
{
return _wType;
}
const void* getValue(ULONG& cb)
{
cb = _cbValue;
return _pvValue;
}
void setValue(const void* pv, ULONG cb)
{
_pvValue = pv, _cbValue = cb;
_wValueLength = (WORD)(_wType ? cb / sizeof(WCHAR) : cb);
}
RsrcNode* find(const PCWSTR strings[], ULONG n);
ULONG GetSize() const;
PVOID Store(PVOID buf, ULONG* pcb) const;
};
bool RsrcNode::ParseResourse(PVOID buf, ULONG size, ULONG* pLength, PCSTR prefix)
{
union {
PVOID pv;
RsrcHeader* ph;
ULONG_PTR up;
PCWSTR sz;
};
pv = buf;
if (size < sizeof(RsrcHeader) || (up & 3))
{
return false;
}
WORD wType = ph->wType;
ULONG wValueLength = ph->wValueLength, wLength = ph->wLength;
ULONG cbValue = 0;
switch (wType)
{
case 1:
cbValue = wValueLength * sizeof(WCHAR);
break;
case 0:
cbValue = wValueLength;
break;
default:
return false;
}
*pLength = wLength;
if (wLength > size || wLength < sizeof(RsrcHeader) || cbValue >= (wLength -= sizeof(RsrcHeader)))
{
return false;
}
wLength -= cbValue;
sz = ph->szKey, _name = sz;
do
{
if (wLength < sizeof(WCHAR))
{
return false;
}
wLength -= sizeof(WCHAR);
} while (*sz++);
DbgPrint("%s%S {\n", prefix, _name);
if (up & 3)
{
if (wLength < 2)
{
return false;
}
up += 2, wLength -= 2;
}
_wType = wType, _wValueLength = (WORD)wValueLength, _cbValue = cbValue, _pvValue = pv;
if (wValueLength && wType)
{
if (sz[wValueLength - 1])
{
return false;
}
DbgPrint("%s\t%S\n", prefix, sz);
}
if (wLength)
{
if (!*--prefix) return false;
up += wValueLength;
do
{
if (up & 3)
{
if (wLength < 2)
{
return false;
}
up += 2;
if (!(wLength -= 2))
{
break;
}
}
if (RsrcNode* node = new RsrcNode(DBG_OPT((prefix))))
{
node->_next = _first, _first = node;
if (node->ParseResourse(ph, wLength, &size, prefix))
{
continue;
}
}
return false;
} while (up += size, wLength -= size);
prefix++;
}
DbgPrint("%s}\n", prefix);
return true;
}
RsrcNode::~RsrcNode()
{
if (RsrcNode* next = _first)
{
do
{
RsrcNode* cur = next;
next = next->_next;
delete cur;
} while (next);
}
DBG_OPT((DbgPrint("%s%S\n", _prefix, _name)));
}
RsrcNode* RsrcNode::find(const PCWSTR strings[], ULONG n)
{
PCWSTR str = *strings++;
if (!str || !wcscmp(str, _name))
{
if (!--n)
{
return this;
}
if (RsrcNode* next = _first)
{
do
{
if (RsrcNode* p = next->find(strings, n))
{
return p;
}
} while (next = next->_next);
}
}
return 0;
}
ULONG RsrcNode::GetSize() const
{
ULONG size = sizeof(RsrcHeader) + (1 + (ULONG)wcslen(_name)) * sizeof(WCHAR);
if (_cbValue)
{
size = ((size + 3) & ~3) + _cbValue;
}
if (RsrcNode* next = _first)
{
do
{
size = ((size + 3) & ~3) + next->GetSize();
} while (next = next->_next);
}
return size;
}
PVOID RsrcNode::Store(PVOID buf, ULONG* pcb) const
{
union {
RsrcHeader* ph;
ULONG_PTR up;
PVOID pv;
};
pv = buf;
ph->wType = _wType;
ph->wValueLength = _wValueLength;
ULONG size = (1 + (ULONG)wcslen(_name)) * sizeof(WCHAR), cb;
memcpy(ph->szKey, _name, size);
up += (size += sizeof(RsrcHeader));
if (_cbValue)
{
up = (up + 3) & ~3;
memcpy(pv, _pvValue, _cbValue);
up += _cbValue;
size = ((size + 3) & ~3) + _cbValue;
}
if (RsrcNode* next = _first)
{
do
{
up = (up + 3) & ~3;
pv = next->Store(pv, &cb);
size = ((size + 3) & ~3) + cb;
} while (next = next->_next);
}
reinterpret_cast<RsrcHeader*>(buf)->wLength = (WORD)size;
*pcb = size;
return pv;
}
with this helper structure we can update version in next way:
#include "VerHlp.h"
BOOL UpdateVersion(PVOID pvVersion, ULONG cbVersion, PVOID& pvNewVersion, ULONG& cbNewVersion)
{
BOOL fOk = FALSE;
char prefix[16];
memset(prefix, '\t', sizeof(prefix));
prefix[RTL_NUMBER_OF(prefix) - 1] = 0;
*prefix = 0;
if (RsrcNode* node = new RsrcNode)
{
if (node->ParseResourse(pvVersion, cbVersion, &cbVersion, prefix + RTL_NUMBER_OF(prefix) - 1))
{
static const PCWSTR str[] = {
L"VS_VERSION_INFO", L"StringFileInfo", 0, L"CompanyName"
};
if (RsrcNode *p = node->find(str, RTL_NUMBER_OF(str)))
{
if (p->IsStringValue())
{
ULONG cb;
const void* pvCompanyName = p->getValue(cb);
DbgPrint("CompanyName: %S\n", pvCompanyName);
static WCHAR CompanyName[] = L"[ New Company Name ]";
if (cb != sizeof(CompanyName) ||
memcmp(pvCompanyName, CompanyName, sizeof(CompanyName)))
{
p->setValue(CompanyName, sizeof(CompanyName));
cbVersion = node->GetSize();
if (pvVersion = LocalAlloc(0, cbVersion))
{
node->Store(pvVersion, &cbNewVersion);
pvNewVersion = pvVersion;
fOk = TRUE;
}
}
}
}
}
delete node;
}
return fOk;
}
struct EnumVerData
{
HANDLE hUpdate;
BOOL fDiscard;
};
BOOL CALLBACK EnumResLangProc(HMODULE hModule,
PCWSTR lpszType,
PCWSTR lpszName,
WORD wIDLanguage,
EnumVerData* Ctx
)
{
if (HRSRC hResInfo = FindResourceExW(hModule, lpszType, lpszName, wIDLanguage))
{
if (HGLOBAL hg = LoadResource(hModule, hResInfo))
{
if (ULONG size = SizeofResource(hModule, hResInfo))
{
if (PVOID pv = LockResource(hg))
{
if (UpdateVersion(pv, size, pv, size))
{
if (UpdateResource(Ctx->hUpdate, lpszType, lpszName, wIDLanguage, pv, size))
{
Ctx->fDiscard = FALSE;
}
LocalFree(pv);
}
}
}
}
}
return TRUE;
}
ULONG UpdateVersion(PCWSTR FileName)
{
ULONG dwError = NOERROR;
EnumVerData ctx;
if (ctx.hUpdate = BeginUpdateResource(FileName, FALSE))
{
ctx.fDiscard = TRUE;
if (HMODULE hmod = LoadLibraryExW(FileName, 0, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE))
{
if (!EnumResourceLanguages(hmod, RT_VERSION,
MAKEINTRESOURCE(VS_VERSION_INFO),
(ENUMRESLANGPROCW)EnumResLangProc, (LONG_PTR)&ctx))
{
dwError = GetLastError();
}
FreeLibrary(hmod);
}
else
{
dwError = GetLastError();
}
if (!dwError && !EndUpdateResourceW(ctx.hUpdate, ctx.fDiscard))
{
dwError = GetLastError();
}
}
else
{
dwError = GetLastError();
}
return dwError;
}

All possible combinations across arrays in nodejs

Lets say I have three arrays:
var arr1 = ['red', 'orange', 'yellow',];
var arr2 = ['car', 'bus'];
var arr3 = ['china'];
How to get the following combinations:
1. Order matters,
2. Each result(can be a array) should contain 0..1 element of each arr
3. Element of arr1,arr2,arr3 can appear 0..1 times :
red,car,china
red,china,car
car,red,china
car,china,red
china,car,red,
china,red,car
red,bus,china
red,china,bus
bus,red,china
bus,china,red
china,bus,red,
china,red,bus
orange,car,china
orange,china,bus
bus,orange,china
bus,china,orange
china,bus,orange,
china,orange,bus
orange,bus,china
orange,china,bus
bus,orange,china
bus,china,orange
china,bus,orange,
yellow,car,china
yellow,china,car
car,yellow,china
car,china,yellow
china,car,yellow,
china,yellow,car
yellow,bus,china
yellow,china,bus
bus,yellow,china
bus,china,yellow
china,bus,yellow,
china,yellow,bus
red,car
car,red
red,bus
bus,red
orange,car
car,orange,
orange,bus
bus,orange,
yellow,car
car,yellow,
yellow,bus
bus,yellow,
red,china
china,red,
orange,china
china,orange,
yellow,china
china,yellow,
car,china
china,car,
bus,china
china,bus,
red,
orange,
yellow,
car,
bus,
china,
What I could not figure out is how to control the appear times in the element of each arr, my code is as follows:
function get_titles_appearances(titles_columns) {
titles_columns = titles_columns || [];
var n = titles_columns.length;
if (n === 0) { return [] }
if (n === 1) { return titles_columns[0] }
var save = function () {
var m = groups.length, c = [];
while (m--) { c[m] = groups[m] }
rtn.push(c);
}
var i = 0, len = 0, counter = [], groups = [], rtn = [];
for (; i < n; i++) {
counter[i] = 0;
groups[i] = titles_columns[i][0];
}
save();
while (true) {
i = n - 1, len = titles_columns[i].length;
if (++counter[i] >= len) {
while (counter[i] >= len) {
if (i === 0) { return rtn }
groups[i] = titles_columns[i][0];
counter[i--] = 0;
counter[i]++;
len = titles_columns[i].length;
}
}
groups[i] = titles_columns[i][counter[i]];
save();
}
}
_.forEach(get_titles_appearances(titles_columns_config_default), function (value) {
// titles_appearances.push( _.join(value, '')+"':'"+_.join(value, ','))
titles_appearances = titles_appearances+"{"+ _.join(value, '')+":"+_.join(value, ',')+"},"
})
titles_columns_config_default refer to [arr1,arr2,arr3]

Resources