Merge "Remove misleading comment."
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 05e6efa..c07a34a 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -169,24 +169,26 @@
return;
}
- const char* signal_name = get_signame(info->si_signo);
- bool has_address = signal_has_si_addr(info->si_signo, info->si_code);
-
- // Many signals don't have an address.
+ // Many signals don't have an address or sender.
char addr_desc[32] = ""; // ", fault addr 0x1234"
- if (has_address) {
+ if (signal_has_si_addr(info)) {
async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr);
}
+ pid_t self_pid = __getpid();
+ char sender_desc[32] = {}; // " from pid 1234, uid 666"
+ if (signal_has_sender(info, self_pid)) {
+ get_signal_sender(sender_desc, sizeof(sender_desc), info);
+ }
char main_thread_name[MAX_TASK_NAME_LEN + 1];
if (!get_main_thread_name(main_thread_name, sizeof(main_thread_name))) {
strncpy(main_thread_name, "<unknown>", sizeof(main_thread_name));
}
- async_safe_format_log(
- ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s), code %d (%s)%s in tid %d (%s), pid %d (%s)",
- info->si_signo, signal_name, info->si_code, get_sigcode(info->si_signo, info->si_code),
- addr_desc, __gettid(), thread_name, __getpid(), main_thread_name);
+ async_safe_format_log(ANDROID_LOG_FATAL, "libc",
+ "Fatal signal %d (%s), code %d (%s%s)%s in tid %d (%s), pid %d (%s)",
+ info->si_signo, get_signame(info), info->si_code, get_sigcode(info),
+ sender_desc, addr_desc, __gettid(), thread_name, self_pid, main_thread_name);
}
/*
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 7b04e71..7c5304e 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -74,8 +74,10 @@
void drop_capabilities();
-bool signal_has_si_addr(int si_signo, int si_code);
-const char* get_signame(int sig);
-const char* get_sigcode(int signo, int code);
+bool signal_has_sender(const siginfo_t*, pid_t caller_pid);
+bool signal_has_si_addr(const siginfo_t*);
+void get_signal_sender(char* buf, size_t n, const siginfo_t*);
+const char* get_signame(const siginfo_t*);
+const char* get_sigcode(const siginfo_t*);
#endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index af8072e..e11be1e 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -102,18 +102,24 @@
if (!cause.empty()) _LOG(log, logtype::HEADER, "Cause: %s\n", cause.c_str());
}
-static void dump_signal_info(log_t* log, const siginfo_t* si) {
+static void dump_signal_info(log_t* log, const ThreadInfo& thread_info) {
char addr_desc[32]; // ", fault addr 0x1234"
- if (signal_has_si_addr(si->si_signo, si->si_code)) {
- snprintf(addr_desc, sizeof(addr_desc), "%p", si->si_addr);
+ if (signal_has_si_addr(thread_info.siginfo)) {
+ snprintf(addr_desc, sizeof(addr_desc), "%p", thread_info.siginfo->si_addr);
} else {
snprintf(addr_desc, sizeof(addr_desc), "--------");
}
- _LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s), fault addr %s\n", si->si_signo,
- get_signame(si->si_signo), si->si_code, get_sigcode(si->si_signo, si->si_code), addr_desc);
+ char sender_desc[32] = {}; // " from pid 1234, uid 666"
+ if (signal_has_sender(thread_info.siginfo, thread_info.pid)) {
+ get_signal_sender(sender_desc, sizeof(sender_desc), thread_info.siginfo);
+ }
- dump_probable_cause(log, si);
+ _LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s%s), fault addr %s\n",
+ thread_info.siginfo->si_signo, get_signame(thread_info.siginfo),
+ thread_info.siginfo->si_code, get_sigcode(thread_info.siginfo), sender_desc, addr_desc);
+
+ dump_probable_cause(log, thread_info.siginfo);
}
static void dump_thread_info(log_t* log, const ThreadInfo& thread_info) {
@@ -412,7 +418,7 @@
dump_thread_info(log, thread_info);
if (thread_info.siginfo) {
- dump_signal_info(log, thread_info.siginfo);
+ dump_signal_info(log, thread_info);
}
if (primary_thread) {
@@ -442,7 +448,7 @@
if (map) {
uint64_t addr = 0;
siginfo_t* si = thread_info.siginfo;
- if (signal_has_si_addr(si->si_signo, si->si_code)) {
+ if (signal_has_si_addr(si)) {
addr = reinterpret_cast<uint64_t>(si->si_addr);
}
dump_all_maps(log, map, process_memory, addr);
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 1ad1800..1f6f3c8 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -254,13 +254,13 @@
}
}
-bool signal_has_si_addr(int si_signo, int si_code) {
+bool signal_has_si_addr(const siginfo_t* si) {
// Manually sent signals won't have si_addr.
- if (si_code == SI_USER || si_code == SI_QUEUE || si_code == SI_TKILL) {
+ if (si->si_code == SI_USER || si->si_code == SI_QUEUE || si->si_code == SI_TKILL) {
return false;
}
- switch (si_signo) {
+ switch (si->si_signo) {
case SIGBUS:
case SIGFPE:
case SIGILL:
@@ -272,8 +272,16 @@
}
}
-const char* get_signame(int sig) {
- switch (sig) {
+bool signal_has_sender(const siginfo_t* si, pid_t caller_pid) {
+ return SI_FROMUSER(si) && (si->si_pid != 0) && (si->si_pid != caller_pid);
+}
+
+void get_signal_sender(char* buf, size_t n, const siginfo_t* si) {
+ snprintf(buf, n, " from pid %d, uid %d", si->si_pid, si->si_uid);
+}
+
+const char* get_signame(const siginfo_t* si) {
+ switch (si->si_signo) {
case SIGABRT: return "SIGABRT";
case SIGBUS: return "SIGBUS";
case SIGFPE: return "SIGFPE";
@@ -290,11 +298,11 @@
}
}
-const char* get_sigcode(int signo, int code) {
+const char* get_sigcode(const siginfo_t* si) {
// Try the signal-specific codes...
- switch (signo) {
+ switch (si->si_signo) {
case SIGILL:
- switch (code) {
+ switch (si->si_code) {
case ILL_ILLOPC: return "ILL_ILLOPC";
case ILL_ILLOPN: return "ILL_ILLOPN";
case ILL_ILLADR: return "ILL_ILLADR";
@@ -307,7 +315,7 @@
static_assert(NSIGILL == ILL_BADSTK, "missing ILL_* si_code");
break;
case SIGBUS:
- switch (code) {
+ switch (si->si_code) {
case BUS_ADRALN: return "BUS_ADRALN";
case BUS_ADRERR: return "BUS_ADRERR";
case BUS_OBJERR: return "BUS_OBJERR";
@@ -317,7 +325,7 @@
static_assert(NSIGBUS == BUS_MCEERR_AO, "missing BUS_* si_code");
break;
case SIGFPE:
- switch (code) {
+ switch (si->si_code) {
case FPE_INTDIV: return "FPE_INTDIV";
case FPE_INTOVF: return "FPE_INTOVF";
case FPE_FLTDIV: return "FPE_FLTDIV";
@@ -330,7 +338,7 @@
static_assert(NSIGFPE == FPE_FLTSUB, "missing FPE_* si_code");
break;
case SIGSEGV:
- switch (code) {
+ switch (si->si_code) {
case SEGV_MAPERR: return "SEGV_MAPERR";
case SEGV_ACCERR: return "SEGV_ACCERR";
#if defined(SEGV_BNDERR)
@@ -350,21 +358,21 @@
break;
#if defined(SYS_SECCOMP) // Our glibc is too old, and we build this for the host too.
case SIGSYS:
- switch (code) {
+ switch (si->si_code) {
case SYS_SECCOMP: return "SYS_SECCOMP";
}
static_assert(NSIGSYS == SYS_SECCOMP, "missing SYS_* si_code");
break;
#endif
case SIGTRAP:
- switch (code) {
+ switch (si->si_code) {
case TRAP_BRKPT: return "TRAP_BRKPT";
case TRAP_TRACE: return "TRAP_TRACE";
case TRAP_BRANCH: return "TRAP_BRANCH";
case TRAP_HWBKPT: return "TRAP_HWBKPT";
}
- if ((code & 0xff) == SIGTRAP) {
- switch ((code >> 8) & 0xff) {
+ if ((si->si_code & 0xff) == SIGTRAP) {
+ switch ((si->si_code >> 8) & 0xff) {
case PTRACE_EVENT_FORK:
return "PTRACE_EVENT_FORK";
case PTRACE_EVENT_VFORK:
@@ -387,7 +395,7 @@
break;
}
// Then the other codes...
- switch (code) {
+ switch (si->si_code) {
case SI_USER: return "SI_USER";
case SI_KERNEL: return "SI_KERNEL";
case SI_QUEUE: return "SI_QUEUE";
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 780ff50..9463cc9 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -693,10 +693,14 @@
fb_queue_notice("--------------------------------------------");
}
-static struct sparse_file** load_sparse_files(int fd, int max_size) {
+static struct sparse_file** load_sparse_files(int fd, int64_t max_size) {
struct sparse_file* s = sparse_file_import_auto(fd, false, true);
if (!s) die("cannot sparse read file");
+ if (max_size <= 0 || max_size > std::numeric_limits<uint32_t>::max()) {
+ die("invalid max size %" PRId64, max_size);
+ }
+
int files = sparse_file_resparse(s, max_size, nullptr, 0);
if (files < 0) die("Failed to resparse");
@@ -1640,20 +1644,20 @@
}
if (wants_wipe) {
- fb_queue_erase("userdata");
- if (set_fbe_marker) {
- fprintf(stderr, "setting FBE marker on initial userdata...\n");
- std::string initial_userdata_dir = create_fbemarker_tmpdir();
- fb_perform_format(transport, "userdata", 1, "", "", initial_userdata_dir);
- delete_fbemarker_tmpdir(initial_userdata_dir);
- } else {
- fb_perform_format(transport, "userdata", 1, "", "", "");
- }
-
- std::string cache_type;
- if (fb_getvar(transport, "partition-type:cache", &cache_type) && !cache_type.empty()) {
- fb_queue_erase("cache");
- fb_perform_format(transport, "cache", 1, "", "", "");
+ std::vector<std::string> partitions = { "userdata", "cache", "metadata" };
+ for (const auto& partition : partitions) {
+ std::string partition_type;
+ if (!fb_getvar(transport, std::string{"partition-type:"} + partition, &partition_type)) continue;
+ if (partition_type.empty()) continue;
+ fb_queue_erase(partition);
+ if (partition == "userdata" && set_fbe_marker) {
+ fprintf(stderr, "setting FBE marker on initial userdata...\n");
+ std::string initial_userdata_dir = create_fbemarker_tmpdir();
+ fb_perform_format(transport, partition, 1, "", "", initial_userdata_dir);
+ delete_fbemarker_tmpdir(initial_userdata_dir);
+ } else {
+ fb_perform_format(transport, partition, 1, "", "", "");
+ }
}
}
if (wants_set_active) {
diff --git a/fastboot/protocol.cpp b/fastboot/protocol.cpp
index a089567..7a333ee 100644
--- a/fastboot/protocol.cpp
+++ b/fastboot/protocol.cpp
@@ -344,12 +344,12 @@
}
int fb_download_data_sparse(Transport* transport, struct sparse_file* s) {
- int size = sparse_file_len(s, true, false);
- if (size <= 0) {
+ int64_t size = sparse_file_len(s, true, false);
+ if (size <= 0 || size > std::numeric_limits<uint32_t>::max()) {
return -1;
}
- std::string cmd(android::base::StringPrintf("download:%08x", size));
+ std::string cmd(android::base::StringPrintf("download:%08" PRIx64, size));
int r = _command_start(transport, cmd, size, 0);
if (r < 0) {
return -1;
diff --git a/init/README.md b/init/README.md
index b14521c..c08b07a 100644
--- a/init/README.md
+++ b/init/README.md
@@ -193,7 +193,7 @@
`disabled`
> This service will not automatically start with its class.
- It must be explicitly started by name.
+ It must be explicitly started by name or by interface name.
`file <path> <type>`
> Open a file path and pass its fd to the launched process. _type_ must be
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 6d00dc6..dd46750 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -164,6 +164,7 @@
shared_libs: ["liblog"],
header_libs: [
+ "libbase_headers",
"libcutils_headers",
"libutils_headers",
],
diff --git a/libcutils/include/cutils/sched_policy.h b/libcutils/include/cutils/sched_policy.h
index 4c1113b..cf91b76 100644
--- a/libcutils/include/cutils/sched_policy.h
+++ b/libcutils/include/cutils/sched_policy.h
@@ -40,16 +40,17 @@
/* Keep in sync with THREAD_GROUP_* in frameworks/base/core/java/android/os/Process.java */
typedef enum {
- SP_DEFAULT = -1,
+ SP_DEFAULT = -1,
SP_BACKGROUND = 0,
SP_FOREGROUND = 1,
- SP_SYSTEM = 2, // can't be used with set_sched_policy()
- SP_AUDIO_APP = 3,
- SP_AUDIO_SYS = 4,
- SP_TOP_APP = 5,
- SP_RT_APP = 6,
+ SP_SYSTEM = 2, // can't be used with set_sched_policy()
+ SP_AUDIO_APP = 3,
+ SP_AUDIO_SYS = 4,
+ SP_TOP_APP = 5,
+ SP_RT_APP = 6,
+ SP_RESTRICTED = 7,
SP_CNT,
- SP_MAX = SP_CNT - 1,
+ SP_MAX = SP_CNT - 1,
SP_SYSTEM_DEFAULT = SP_FOREGROUND,
} SchedPolicy;
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index bbb150d..58b9f09 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -74,7 +74,8 @@
#define ATRACE_TAG_ADB (1<<22)
#define ATRACE_TAG_VIBRATOR (1<<23)
#define ATRACE_TAG_AIDL (1<<24)
-#define ATRACE_TAG_LAST ATRACE_TAG_AIDL
+#define ATRACE_TAG_NNAPI (1<<25)
+#define ATRACE_TAG_LAST ATRACE_TAG_NNAPI
// Reserved for initialization.
#define ATRACE_TAG_NOT_READY (1ULL<<63)
diff --git a/libcutils/sched_policy.cpp b/libcutils/sched_policy.cpp
index f5ce82f..3fa548f 100644
--- a/libcutils/sched_policy.cpp
+++ b/libcutils/sched_policy.cpp
@@ -25,6 +25,7 @@
#include <string.h>
#include <unistd.h>
+#include <android-base/macros.h>
#include <log/log.h>
/* Re-map SP_DEFAULT to the system default policy, and leave other values unchanged.
@@ -57,6 +58,7 @@
static int bg_cpuset_fd = -1;
static int fg_cpuset_fd = -1;
static int ta_cpuset_fd = -1; // special cpuset for top app
+static int rs_cpuset_fd = -1; // special cpuset for screen off restrictions
// File descriptors open to /dev/stune/../tasks, setup by initialize, or -1 on error
static int bg_schedboost_fd = -1;
@@ -151,6 +153,8 @@
system_bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
filename = "/dev/cpuset/top-app/tasks";
ta_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
+ filename = "/dev/cpuset/restricted/tasks";
+ rs_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
if (schedboost_enabled()) {
filename = "/dev/stune/top-app/tasks";
@@ -308,6 +312,9 @@
case SP_SYSTEM:
fd = system_bg_cpuset_fd;
break;
+ case SP_RESTRICTED:
+ fd = rs_cpuset_fd;
+ break;
default:
boost_fd = fd = -1;
break;
@@ -454,20 +461,16 @@
#endif
-const char *get_sched_policy_name(SchedPolicy policy)
-{
+const char* get_sched_policy_name(SchedPolicy policy) {
policy = _policy(policy);
- static const char * const strings[SP_CNT] = {
- [SP_BACKGROUND] = "bg",
- [SP_FOREGROUND] = "fg",
- [SP_SYSTEM] = " ",
- [SP_AUDIO_APP] = "aa",
- [SP_AUDIO_SYS] = "as",
- [SP_TOP_APP] = "ta",
- [SP_RT_APP] = "rt",
+ static const char* const kSchedPolicyNames[] = {
+ [SP_BACKGROUND] = "bg", [SP_FOREGROUND] = "fg", [SP_SYSTEM] = " ",
+ [SP_AUDIO_APP] = "aa", [SP_AUDIO_SYS] = "as", [SP_TOP_APP] = "ta",
+ [SP_RT_APP] = "rt", [SP_RESTRICTED] = "rs",
};
- if ((policy < SP_CNT) && (strings[policy] != NULL))
- return strings[policy];
- else
+ static_assert(arraysize(kSchedPolicyNames) == SP_CNT, "missing name");
+ if (policy < SP_BACKGROUND || policy >= SP_CNT) {
return "error";
+ }
+ return kSchedPolicyNames[policy];
}
diff --git a/libcutils/tests/sched_policy_test.cpp b/libcutils/tests/sched_policy_test.cpp
index 173174a..5942ee5 100644
--- a/libcutils/tests/sched_policy_test.cpp
+++ b/libcutils/tests/sched_policy_test.cpp
@@ -60,6 +60,12 @@
return sleepTimes[median];
}
+static void AssertPolicy(SchedPolicy expected_policy) {
+ SchedPolicy current_policy;
+ ASSERT_EQ(0, get_sched_policy(0, ¤t_policy));
+ EXPECT_EQ(expected_policy, current_policy);
+}
+
TEST(SchedPolicy, set_sched_policy) {
if (!hasCapSysNice()) {
GTEST_LOG_(INFO) << "skipping test that requires CAP_SYS_NICE";
@@ -76,23 +82,17 @@
const unsigned int BG_FG_SLACK_FACTOR = 100;
ASSERT_EQ(0, set_sched_policy(0, SP_BACKGROUND));
+ AssertPolicy(SP_BACKGROUND);
auto bgSleepTime = medianSleepTime();
ASSERT_EQ(0, set_sched_policy(0, SP_FOREGROUND));
+ AssertPolicy(SP_FOREGROUND);
auto fgSleepTime = medianSleepTime();
ASSERT_GT(bgSleepTime, fgSleepTime * BG_FG_SLACK_FACTOR);
}
-TEST(SchedPolicy, get_sched_policy) {
- SchedPolicy policy;
- ASSERT_EQ(0, get_sched_policy(0, &policy));
-
- const char *policyName = get_sched_policy_name(policy);
- EXPECT_NE(nullptr, policyName);
- EXPECT_STRNE("error", policyName);
-
- ASSERT_EQ(0, set_sched_policy(0, SP_BACKGROUND));
- SchedPolicy newPolicy;
- ASSERT_EQ(0, get_sched_policy(0, &newPolicy));
- EXPECT_EQ(SP_BACKGROUND, newPolicy);
+TEST(SchedPolicy, get_sched_policy_name) {
+ EXPECT_STREQ("bg", get_sched_policy_name(SP_BACKGROUND));
+ EXPECT_STREQ("error", get_sched_policy_name(SchedPolicy(-2)));
+ EXPECT_STREQ("error", get_sched_policy_name(SP_CNT));
}
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index ed1b9bc..6c06618 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -93,6 +93,10 @@
host_supported: true,
defaults: ["libziparchive_flags"],
+ data: [
+ "testdata/**/*",
+ ],
+
srcs: [
"entry_name_utils_test.cc",
"zip_archive_test.cc",
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index ad673dc..377479f 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -34,7 +34,7 @@
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_archive_stream_entry.h>
-static std::string test_data_dir;
+static std::string test_data_dir = android::base::GetExecutableDirectory() + "/testdata";
static const std::string kMissingZip = "missing.zip";
static const std::string kValidZip = "valid.zip";
@@ -729,41 +729,3 @@
ASSERT_EQ(0u, writer.GetOutput().size());
}
}
-
-int main(int argc, char** argv) {
- ::testing::InitGoogleTest(&argc, argv);
-
- static struct option options[] = {{"test_data_dir", required_argument, nullptr, 't'},
- {nullptr, 0, nullptr, 0}};
-
- while (true) {
- int option_index;
- const int c = getopt_long_only(argc, argv, "", options, &option_index);
- if (c == -1) {
- break;
- }
-
- if (c == 't') {
- test_data_dir = optarg;
- }
- }
-
- if (test_data_dir.size() == 0) {
- printf("Test data flag (--test_data_dir) required\n\n");
- return -1;
- }
-
- if (test_data_dir[0] != '/') {
- std::vector<char> cwd_buffer(1024);
- const char* cwd = getcwd(cwd_buffer.data(), cwd_buffer.size() - 1);
- if (cwd == nullptr) {
- printf("Cannot get current working directory, use an absolute path instead, was %s\n\n",
- test_data_dir.c_str());
- return -2;
- }
- test_data_dir = '/' + test_data_dir;
- test_data_dir = cwd + test_data_dir;
- }
-
- return RUN_ALL_TESTS();
-}
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index 7a7ac7d..ab980ac 100755
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -825,7 +825,7 @@
(unsigned short)n);
// notify readers
- if (!rc) {
+ if (rc > 0) {
reader->notifyNewLog(static_cast<log_mask_t>(1 << LOG_ID_KERNEL));
}
diff --git a/rootdir/etc/public.libraries.iot.txt b/rootdir/etc/public.libraries.iot.txt
new file mode 100644
index 0000000..ff0813d
--- /dev/null
+++ b/rootdir/etc/public.libraries.iot.txt
@@ -0,0 +1,27 @@
+# See https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md
+libandroid.so
+libandroidthings.so
+libaaudio.so
+libc.so
+libcamera2ndk.so
+libdl.so
+libEGL.so
+libGLESv1_CM.so
+libGLESv2.so
+libGLESv3.so
+libicui18n.so
+libicuuc.so
+libjnigraphics.so
+liblog.so
+libmediandk.so
+libm.so
+libnativewindow.so
+libneuralnetworks.so
+libOpenMAXAL.so
+libOpenSLES.so
+libRS.so
+libstdc++.so
+libsync.so
+libvulkan.so
+libwebviewchromium_plat_support.so
+libz.so