0CCh Blog

不要使用GetFullPathName获得相对路径的全路径

上周末在网上闲逛,看到一篇介绍Path*相关API的文章,发现文章中推荐了GetFullPathName这个API,因为他能方便的获得相对路径对于当前工作目录的全路径。然而,我对于这个推荐API是坚决反对的。如果所有工程代码都是一个人开发,这种情况下调用这个API,我认为尚可理解,但是如果对于多人维护的大型工程,就不要使用它了。原因很简单,这个API是根据当前工作目录去确定绝对路径的,然而你无法确定当前工作目录是否是你认为的工作目录。即使在调用GetFullPathName之前,检查了这个路径,我认为也是不可信的。因为在检查过后,也有可能在其他线程中改变当前工作目录,要知道这个变量是全局唯一的。

所以,以下代码是不可取的:

WCHAR lpBuffer[MAX_PATH];
LPWSTR lpFname = NULL;

SetCurrentDirectoryW(L"C:\\some\\thing\\dir");
GetFullPathNameW(L"..\\other\\dir", MAX_PATH, lpBuffer, &lpFname);

其实微软为我们提供挺好的API来代替他,比如PathCanonicalize以及PathCombine(实际上有更安全的API,比如 PathCchCanonicalize和PathCchCombine,只不过需要高版本的系统)。

所以,以下代码是正确的:

WCHAR lpBuffer[MAX_PATH];
WCHAR lpSrc[MAX_PATH] = L"C:\\some\\thing\\dir";
wcscat_s(lpSrc, L"..\\other\\dir");
PathCanonicalizeW(lpBuffer, lpSrc);
WCHAR lpBuffer[MAX_PATH];
PathCombineW(lpBuffer, L"C:\\some\\thing\\dir", L"..\\other\\dir");

最后在介绍一个实用的函数:PathCompactPath,这个函数把路径缩写为指定的像素长度:

WCHAR buffer[MAX_PATH] = L"C:\\some\\thing\\very\\long\\long\\long\\long\\long\\path";
PathCompactPathW(NULL, buffer, 200);

// buffer="C:\some\thing\very...\path"

关于TokenLinkedToken的一点记录2

内核原理

0: kd> !process 0 1 explorer.exe
PROCESS ffffc306d0c34580
SessionId: 11 Cid: 44e0 Peb: 006fc000 ParentCid: 5758
DirBase: 2b1d00002 ObjectTable: ffff8b8e568c7040 HandleCount: 29648.
Image: explorer.exe
VadRoot ffffc306ddf5fca0 Vads 1625 Clone 0 Private 403154. Modified 771103. Locked 50.
DeviceMap ffff8b8e3147ed30
Token ffff8b8e4937f940
ElapsedTime 06:29:46.426
UserTime 00:00:48.921
KernelTime 00:00:53.250
QuotaPoolUsage[PagedPool] 2123416
QuotaPoolUsage[NonPagedPool] 225280
Working Set Sizes (now,min,max) (56547, 50, 345) (226188KB, 200KB, 1380KB)
PeakWorkingSetSize 462611
VirtualSize 2103798 Mb
PeakVirtualSize 2104556 Mb
PageFaultCount 2358568
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 426963

0: kd> !token ffff8b8e4937f940
_TOKEN 0xffff8b8e4937f940
TS Session ID: 0xb
User: S-1-5-21-3854333306-943506906-3328512208-1001
User Groups:
00 S-1-5-21-3854333306-943506906-3328512208-513
Attributes - Mandatory Default Enabled
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-114
Attributes - DenyOnly
03 S-1-5-21-3854333306-943506906-3328512208-1002
Attributes - Mandatory Default Enabled
04 S-1-5-32-544
Attributes - DenyOnly
05 S-1-5-32-559
Attributes - Mandatory Default Enabled
06 S-1-5-32-545
Attributes - Mandatory Default Enabled
07 S-1-5-4
Attributes - Mandatory Default Enabled
08 S-1-2-1
Attributes - Mandatory Default Enabled
09 S-1-5-11
Attributes - Mandatory Default Enabled
10 S-1-5-15
Attributes - Mandatory Default Enabled
11 S-1-5-113
Attributes - Mandatory Default Enabled
12 S-1-5-5-0-4234506195
Attributes - Mandatory Default Enabled LogonId
13 S-1-2-0
Attributes - Mandatory Default Enabled
14 S-1-5-64-10
Attributes - Mandatory Default Enabled
15 S-1-16-8192
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-3854333306-943506906-3328512208-513
Privs:
19 0x000000013 SeShutdownPrivilege Attributes - Enabled
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
25 0x000000019 SeUndockPrivilege Attributes -
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes -
34 0x000000022 SeTimeZonePrivilege Attributes -
Authentication ID: (0,fc65706c)
Impersonation Level: Anonymous
TokenType: Primary
Source: User32 TokenFlags: 0x2a00 ( Token in use )
Token ID: fc664685 ParentToken ID: fc65706f
Modified ID: (0, fe4096af)
RestrictedSidCount: 0 RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0 Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:


0: kd> dt _TOKEN 0xffff8b8e4937f940
nt!_TOKEN
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x7fffffff`ffffffff
+0x030 TokenLock : 0xffffc306`c7b5dd40 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
+0x058 AuditPolicy : _SEP_AUDIT_POLICY
+0x078 SessionId : 0xb
+0x07c UserAndGroupCount : 0x11
+0x080 RestrictedSidCount : 0
+0x084 VariableLength : 0x228
+0x088 DynamicCharged : 0x1000
+0x08c DynamicAvailable : 0
+0x090 DefaultOwnerIndex : 0
+0x098 UserAndGroups : 0xffff8b8e`4937fdd0 _SID_AND_ATTRIBUTES
+0x0a0 RestrictedSids : (null)
+0x0a8 PrimaryGroup : 0xffff8b8e`2b2a3b10 Void
+0x0b0 DynamicPart : 0xffff8b8e`2b2a3b10 -> 0x501
+0x0b8 DefaultDacl : 0xffff8b8e`2b2a3b2c _ACL
+0x0c0 TokenType : 1 ( TokenPrimary )
+0x0c4 ImpersonationLevel : 0 ( SecurityAnonymous )
+0x0c8 TokenFlags : 0x2a00
+0x0cc TokenInUse : 0x1 ''
+0x0d0 IntegrityLevelIndex : 0x10
+0x0d4 MandatoryPolicy : 3
+0x0d8 LogonSession : 0xffff8b8e`143fc870 _SEP_LOGON_SESSION_REFERENCES
+0x0e0 OriginatingLogonSession : _LUID
+0x0e8 SidHash : _SID_AND_ATTRIBUTES_HASH
+0x1f8 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH
+0x308 pSecurityAttributes : 0xffff8b8e`0c3b7f30 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
+0x310 Package : (null)
+0x318 Capabilities : (null)
+0x320 CapabilityCount : 0
+0x328 CapabilitiesHash : _SID_AND_ATTRIBUTES_HASH
+0x438 LowboxNumberEntry : (null)
+0x440 LowboxHandlesEntry : (null)
+0x448 pClaimAttributes : (null)
+0x450 TrustLevelSid : (null)
+0x458 TrustLinkedToken : (null)
+0x460 IntegrityLevelSidValue : (null)
+0x468 TokenSidValues : (null)
+0x470 IndexEntry : 0xffff8b8e`349cd270 _SEP_LUID_TO_INDEX_MAP_ENTRY
+0x478 DiagnosticInfo : (null)
+0x480 BnoIsolationHandlesEntry : (null)
+0x488 SessionObject : 0xffffc306`cd464140 Void
+0x490 VariablePart : 0xffff8b8e`4937fee0

0: kd> dt 0xffff8b8e`143fc870 _SEP_LOGON_SESSION_REFERENCES
nt!_SEP_LOGON_SESSION_REFERENCES
+0x000 Next : (null)
+0x008 LogonId : _LUID
+0x010 BuddyLogonId : _LUID
+0x018 ReferenceCount : 0n1799
+0x020 Flags : 0xd
+0x028 pDeviceMap : 0xffff8b8e`3147ed30 _DEVICE_MAP
+0x030 Token : 0xffff8b8e`20c65060 Void
+0x038 AccountName : _UNICODE_STRING "win"
+0x048 AuthorityName : _UNICODE_STRING "DESKTOP-GJGV2E2"
+0x058 CachedHandlesTable : _SEP_CACHED_HANDLES_TABLE
+0x068 SharedDataLock : _EX_PUSH_LOCK
+0x070 SharedClaimAttributes : (null)
+0x078 SharedSidValues : (null)
+0x080 RevocationBlock : _OB_HANDLE_REVOCATION_BLOCK
+0x0a0 ServerSilo : (null)
+0x0a8 SiblingAuthId : _LUID
+0x0b0 TokenList : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]


0: kd> dt 0xffff8b8e`143fc870 _SEP_LOGON_SESSION_REFERENCES BuddyLogonId.
nt!_SEP_LOGON_SESSION_REFERENCES
+0x010 BuddyLogonId :
+0x000 LowPart : 0xfc657047
+0x004 HighPart : 0n0

0: kd> ? 0xfc657047&0xf
Evaluate expression: 7 = 00000000`00000007

0: kd> dq nt!SepLogonSessions L1
fffff802`45a744a0 ffff8b8e`0b0020d0
0: kd> dq ffff8b8e`0b0020d0+8*7 L1
ffff8b8e`0b002108 ffff8b8e`367ef010

0: kd> dt ffff8b8e`367ef010 _SEP_LOGON_SESSION_REFERENCES
nt!_SEP_LOGON_SESSION_REFERENCES
+0x000 Next : 0xffff8b8e`593ba230 _SEP_LOGON_SESSION_REFERENCES
+0x008 LogonId : _LUID
+0x010 BuddyLogonId : _LUID
+0x018 ReferenceCount : 0n56
+0x020 Flags : 0xa
+0x028 pDeviceMap : 0xffff8b8e`0e5e0890 _DEVICE_MAP
+0x030 Token : 0xffff8b8e`15b56940 Void
+0x038 AccountName : _UNICODE_STRING "win"
+0x048 AuthorityName : _UNICODE_STRING "DESKTOP-GJGV2E2"
+0x058 CachedHandlesTable : _SEP_CACHED_HANDLES_TABLE
+0x068 SharedDataLock : _EX_PUSH_LOCK
+0x070 SharedClaimAttributes : (null)
+0x078 SharedSidValues : (null)
+0x080 RevocationBlock : _OB_HANDLE_REVOCATION_BLOCK
+0x0a0 ServerSilo : (null)
+0x0a8 SiblingAuthId : _LUID
+0x0b0 TokenList : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]

0: kd> !token 0xffff8b8e`15b56940
_TOKEN 0xffff8b8e15b56940
TS Session ID: 0xb
User: S-1-5-21-3854333306-943506906-3328512208-1001
User Groups:
00 S-1-5-21-3854333306-943506906-3328512208-513
Attributes - Mandatory Default Enabled
01 S-1-1-0
Attributes - Mandatory Default Enabled
02 S-1-5-114
Attributes - Mandatory Default Enabled
03 S-1-5-21-3854333306-943506906-3328512208-1002
Attributes - Mandatory Default Enabled
04 S-1-5-32-544
Attributes - Mandatory Default Enabled Owner
05 S-1-5-32-559
Attributes - Mandatory Default Enabled
06 S-1-5-32-545
Attributes - Mandatory Default Enabled
07 S-1-5-4
Attributes - Mandatory Default Enabled
08 S-1-2-1
Attributes - Mandatory Default Enabled
09 S-1-5-11
Attributes - Mandatory Default Enabled
10 S-1-5-15
Attributes - Mandatory Default Enabled
11 S-1-5-113
Attributes - Mandatory Default Enabled
12 S-1-5-5-0-4234506195
Attributes - Mandatory Default Enabled LogonId
13 S-1-2-0
Attributes - Mandatory Default Enabled
14 S-1-5-64-10
Attributes - Mandatory Default Enabled
15 S-1-16-12288
Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-3854333306-943506906-3328512208-513
Privs:
05 0x000000005 SeIncreaseQuotaPrivilege Attributes -
08 0x000000008 SeSecurityPrivilege Attributes -
09 0x000000009 SeTakeOwnershipPrivilege Attributes -
10 0x00000000a SeLoadDriverPrivilege Attributes -
11 0x00000000b SeSystemProfilePrivilege Attributes -
12 0x00000000c SeSystemtimePrivilege Attributes -
13 0x00000000d SeProfileSingleProcessPrivilege Attributes -
14 0x00000000e SeIncreaseBasePriorityPrivilege Attributes -
15 0x00000000f SeCreatePagefilePrivilege Attributes -
17 0x000000011 SeBackupPrivilege Attributes -
18 0x000000012 SeRestorePrivilege Attributes -
19 0x000000013 SeShutdownPrivilege Attributes -
20 0x000000014 SeDebugPrivilege Attributes -
22 0x000000016 SeSystemEnvironmentPrivilege Attributes -
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default
24 0x000000018 SeRemoteShutdownPrivilege Attributes -
25 0x000000019 SeUndockPrivilege Attributes -
28 0x00000001c SeManageVolumePrivilege Attributes -
29 0x00000001d SeImpersonatePrivilege Attributes - Enabled Default
30 0x00000001e SeCreateGlobalPrivilege Attributes - Enabled Default
33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes -
34 0x000000022 SeTimeZonePrivilege Attributes -
35 0x000000023 SeCreateSymbolicLinkPrivilege Attributes -
36 0x000000024 SeDelegateSessionUserImpersonatePrivilege Attributes -
Authentication ID: (0,fc657047)
Impersonation Level: Anonymous
TokenType: Primary
Source: User32 TokenFlags: 0x2020 ( Token NOT in use )
Token ID: fc657079 ParentToken ID: 0
Modified ID: (0, fc65706b)
RestrictedSidCount: 0 RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0 Capabilities: 0x0000000000000000
LowboxNumberEntry: 0x0000000000000000
Security Attributes:

关于TokenLinkedToken的一点记录

我们用GetTokenInformation可以获得一个TokenLinkedToken,简单的说就是要获得与我们进程token关联的token。

接下来就有趣了,如果当你的进程是一个提权的管理员权限的进程,那么你获得的token会是一个标准用户进程的token,也就是一个提权之前进程。那么这有什么用呢?比如我们的子程序需要运行其他开发者开发的插件,而我们不想给予他们过高的权限,那么这个就有用了。当然,如果你更谨慎一些,你希望给予他更低的权限,那就得实用CreateRestrictedToken来创建一个新的token了。

聪明的程序员看到这里肯定就会想,既然管理员权限下的进程获得的TokenLinkedToken是一个标准用户权限的token,那么标准用户权限环境下的进程能不能获得一个管理员权限的TokenLinkedToken呢?没错,答案是可以。更聪明的程序员肯定会惊讶,那这个不是安全漏洞么?答案是并不是,因为虽然可以获得一个管理员权限的token,但是这个token只是一个IDENTIFY level token,这是一个token的_SECURITY_IMPERSONATION_LEVEL,不同的模仿等级,对应于不同的功能。比如SecurityIdentification,这个等级就只能用来查询token的信息。比方说有外部一个进程访问我们的进程,我们可以让他提供token验证其身份。但是外部进程为了防止我们用他的token干坏事,所以只给我们一个IDENTIFY level token,这样一来,我们就只能验证身份而无法做其他事情了。

我们真的没办法通过TokenLinkedToken获得可以使用的管理员身份的token了么?也不是,我们确实有办法获得能够使用的管理员身份的token。但是有个前提,我们的进程必须有SeTcbPrivilege权限。那这不也是个安全漏洞么?不是,因为SeTcbPrivilege是SYSTEM用户的权限,简单的说,这个用户的权限比管理员还要高。那这玩意不是也没什么用么?也有用,当你想在系统服务中启动一个管理员身份的进程的时候,可以先获得标准用户权限的token,然后获得其TokenLinkedToken,最后CreateProcessAsUser来创建进程。

TokenLinkedToken

tensorflow入门 —— mnist

自从有tensorflow这样的平台工具横空出世,机器学习的代码编写逐渐变的平民化了。我们不需要太多的数学理论知识就能够完成一些机器学习的项目。比如在使用tensorflow的时候,我们只需要定义好损失函数,tensorflow会自动的帮我们完成反向传播改善参数。我们所需要做的就是合理利用tensorflow创建模型。

当然,我并不是在鼓动初学者跳过数学原理部分,而是认为,如果没有数学基础,入门就死磕原理容易产生挫败感导致放弃。这样就不如先接触简单的项目,在有了一定的体会后,再回头去看看数学原理的东西,这样会更容易接受。比如,当能用tensorflow完成对mnist的训练之后,再去理解反向传播这四个公式,应该会更有感觉。

接下来相信看看这份代码

# -*- coding: utf-8 -*-
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data

# step1
mnist = input_data.read_data_sets(".", one_hot=True)

# step2
INPUT_PARAMETERS = 784
L1_PARAMETERS = 300
W1 = tf.Variable(tf.truncated_normal([INPUT_PARAMETERS, L1_PARAMETERS], stddev=0.1))
b1 = tf.Variable(tf.truncated_normal([L1_PARAMETERS], stddev=0.1))
W2 = tf.Variable(tf.truncated_normal([L1_PARAMETERS, 10], stddev=0.1))
b2 = tf.Variable(tf.truncated_normal([10], stddev=0.1))

# step3
x = tf.placeholder(tf.float32, [None, INPUT_PARAMETERS])
y_ = tf.placeholder(tf.float32, [None, 10])

# step4
hidden1 = tf.nn.sigmoid(tf.matmul(x, W1) + b1)
y = tf.matmul(hidden1, W2) + b2

# step5
loss = (tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
logits=y, labels=y_)))
train_step = tf.train.GradientDescentOptimizer(0.3).minimize(loss)

# step6
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# step7
loss_array = []
accuracy_array = []
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(10000):
batch_xs, batch_ys = mnist.train.next_batch(100)
_, step_loss = sess.run([train_step, loss], feed_dict={x: batch_xs, y_: batch_ys})
if i % 500 == 0:
step_accuracy = accuracy.eval({x: mnist.test.images, y_: mnist.test.labels})
loss_array.append(step_loss)
accuracy_array.append(step_accuracy)
print(step_loss, step_accuracy)

loss_array.append(step_loss)
accuracy_array.append(step_accuracy)
print(step_loss, step_accuracy)

plt.plot([i*500 for i in range(len(loss_array))], loss_array, 'b-',
[i*500 for i in range(len(accuracy_array))], accuracy_array, 'r-')

step1:read_data_sets是tf提供读取mnist数据的函数,运行时会有函数过时的警告提示,但是不用管它,因为用起来完全没有问题。one_hot参数在这里很重要,他将实际的标签值转化为一个向量,例如,将标签值3,转化成表示0-9是否置位的向量:\( \left(\begin{array}{cccccccccc}0;0;0;1;0;0;0;0;0;0;\end{array}\right)^T \)

step2:创建权重和偏置,创建方法有很多,我用的是期望为0,标准差为0.1的随机分布。

step3:创建输入层和标签的placeholder,大概的意思是占住空间,以方便后续数据喂给模型。

step4:实际上是向前传播 \( z = w^{T}x,+,b\),\( a = sigmoid(z)\),其中sigmoid是\( \frac{1}{1+\exp(-z)}\)。这里可能有一个疑问,第二个隐藏层到输出层,没有使用sigmoid函数。原因继续往下看就知道了。

step5:计算损失函数,调用了tf的sigmoid_cross_entropy_with_logits,这个函数把交叉熵和sigmoid的计算放在了一起,所以上面不需要去计算sigmoid了。这样做的好处很明显,就是方便我们修改从最后一个隐藏层到输出层的激活函数,比如将sigmoid_cross_entropy_with_logits替换为softmax_cross_entropy_with_logits,那么我们最后一个激活函数就变成了softmax。最后设置学习率并且把我们的损失函数传给梯度下降类的最小化函数中,tf就会自动的帮我们优化参数,从而最小化损失值了。

step6:使用测试数据去计算模型的识别准确率

step7:最后一步,我们将数据分为小份,随着迭代,逐步喂给模型。然后记录损失和准确率的变化,并做图。

这里我们为了简单,没有使用softmax,dropout和正则化等优化方法,所以识别率只达到了90%,不过作为一个入门来说已经够了。

2018-07-07-tensorflow-mnist

机器学习练习题1

掐指一算,已经有三个月没有更新blog了。因为最近一直在学习机器学习的内容,所以没空也没有新鲜的技术值得写下来分享。还好经过一段时间的积累(学习线性代数和概率论),机器学习这块内容也算是入门了。这是机器学习的第一个习题,线性回归。用最直白的话来说,就是用函数去拟合数据分布,从而达到预测新数据的效果。需要的知识是矩阵的计算,最小二乘法以及求偏微分。

关键的公式只有两个:

那么最后需要确定的只剩下一个,我们希望用什么样的曲线去拟合样本,这个就需要经验和尝试了。这里的练习只需要用直线来拟合就足够了: \( h_\theta(x) = \theta^{T}x = \theta_{0}+\theta_{1}x_{1} \) .

# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

data = np.loadtxt('ex1data1.txt', delimiter=',');

# 分离样本数据输入X和输出Y
X = np.concatenate((np.ones((len(data),1)), data[:,0].reshape((len(data),1))), axis=1)
theta = np.random.randn(2,1)
Y = data[:,1].reshape((len(data),1))

# 为了加速计算,把除法优化位乘法
alpha = 0.01
div_m = 1 / len(data);

for loop_count in range(1000):
Y1 = X.dot(theta)
# 根据公式计算损失
cost = ((Y-Y1)**2).sum() * 0.5 * div_m;
print(cost)
# 更新参数
for i in range(2):
theta[i, 0] = theta[i, 0] - alpha * div_m * (np.diagflat(X[:, i]) * (Y1 - Y)).sum()

# 最后把拟合直线画出来
Xl = np.linspace(0, 30, 100)
Yl = Xl * theta[1, 0] + theta[0, 0]

plt.plot(data[:,0],data[:,1],'x', Xl, Yl, 'r')

2018-06-23-machine-learning-ex1

广度遍历删除文件

最近遇到一个要删除文件夹的问题,文件夹内有大量文件和子文件夹,而且结构非常复杂,删除特别慢。于是思考了一下如何能加速文件删除的问题。我看到大部分的实现方法都是深度遍历,即遇到新的文件夹就进入文件夹遍历文件,直到结束后返回上一层继续遍历。实际上这种方法存在一个问题,在我们的硬盘上,文件夹和文件一般是B-Tree分布的,所以同一层文件夹的文件的数据都比较相近,而不同的层的文件夹的文件可能差距比较远,于是深度遍历让硬盘磁头从一个文件夹跨越到另外一个文件夹,磁头花的时间更长了,更何况子文件夹遍历结束,还得从子文件夹在移动回父文件夹。而是用广度遍历就不同,他把当前目录遍历完成后才去遍历另外一个,这样磁头移动的总距离肯定更少,遍历速度当然更快。以下代码我的一个实现:


#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <vector>
#include <stack>
#include <string>

class AutoFindHandle {
public:
AutoFindHandle(HANDLE find_handle) : find_handle_(find_handle) {}
~AutoFindHandle() {
FindClose(find_handle_);
}

private:
HANDLE find_handle_;
};

BOOL CollectFileAndDir(const std::wstring &dir, std::vector<std::wstring> &file_path, std::vector<std::wstring> &dir_path)
{
HANDLE find_handle;
WIN32_FIND_DATAW data;
std::wstring temp_path;
std::wstring current_dir = dir;
std::stack<std::wstring> temp_dir;

if (current_dir[current_dir.length() - 1] != L'\\') {
current_dir += L"\\";
}

std::wstring path = current_dir + L"*";

find_handle = FindFirstFileW(path.c_str(), &data);
if (find_handle == INVALID_HANDLE_VALUE) {
return TRUE;
}

AutoFindHandle auto_find_handle(find_handle);

if (wcscmp(data.cFileName, L"..") != 0 &&
wcscmp(data.cFileName, L".") != 0) {

temp_path = current_dir;
temp_path += data.cFileName;

if ((data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) != 0) {
SetFileAttributesW(temp_path.c_str(), FILE_ATTRIBUTE_NORMAL);
}

if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {

temp_dir.push(temp_path);

}
else {

file_path.push_back(temp_path);

}
}

while (FindNextFileW(find_handle, &data)) {

if (wcscmp(data.cFileName, L"..") != 0 &&
wcscmp(data.cFileName, L".") != 0) {

temp_path = current_dir;
temp_path += data.cFileName;

if ((data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) != 0) {
SetFileAttributesW(temp_path.c_str(), FILE_ATTRIBUTE_NORMAL);
}

if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {

temp_dir.push(temp_path);

}
else {

file_path.push_back(temp_path);

}
}

}

while (!temp_dir.empty()) {

CollectFileAndDir(temp_dir.top(), file_path, dir_path);

temp_dir.pop();
}


dir_path.push_back(dir);
return TRUE;
}


BOOL DeleteFolder(LPCWSTR path)
{
std::vector<std::wstring> file_path;
std::vector<std::wstring> dir_path;
if (!CollectFileAndDir(path, file_path, dir_path)) {
return FALSE;
}


for (std::vector<std::wstring>::iterator it = file_path.begin(); it != file_path.end(); ++it) {
DeleteFileW(it->c_str());
}

for (std::vector<std::wstring>::iterator it = dir_path.begin(); it != dir_path.end(); ++it) {
RemoveDirectoryW(it->c_str());
}

return TRUE;
}

鸡年总结,狗年计划

虽然没到十五,但是这个年基本上也是过完了。是收心总结鸡年,展望狗年的时候了。

生活方面,鸡年过的不算顺心,尤其是上半年,麻烦事情接二连三,具体也事情也就不想多写了,不愉快的事情就应该被忘记。工作上,虽然没有明显的进步,但也算是得到了不错的回报,内心有愧的拿了一个优秀员工。重点来了,要说说技术方面的事情。去年下半年快到年底的时候,我做了一个决定,学习深度学习相关知识,作为今后转型的砝码。这里不得不说,做这个决定其实已经很晚了,因为已经有很多人早就踏入了这个领域。我总是想,如果能早两年开窍就好了,但是凡是哪有如果呢。更何况就在去年初我还在跟以前同事争论,坚定的认为个人PC行业还是非常宽阔的,但实际上那个想法也只是说自己的一厢情愿而已。下半年某企业找到我说想让我去他们的个人PC业务部门,给的等级还不错,薪资水平也很快给我翻倍了,但是我拒绝了,那个时候我就知道自己内心里已经不看好个人PC,尤其是Windows上面的发展了。于是乎,打算学习更接近未来趋势的一些东西。

当今最火的两个领域,区块链和深度学习,我很快的就选择学习深度学习的相关知识了。原因很简单,深度学习现在已经在很大程度上推进了社会发展。而区块链技术,除了在各种加密币方面非常火之外,貌似还没看到什么用途能促进社会进步,至少没有深度学习那么明显吧。下半年的两三个月里开始慢慢接触了深度学习,当然也包括一些其他的机器学习领域的知识。于是深深的感慨,数学在这些领域的重要性,当然又一次后悔没有学好数学,也不得不吐槽我们的高等数学教育是多么的死板无趣。

今年计划,生活上的事情其实没有什么计划可言,就希望过的更加顺心吧。工作方面,希望能主导一些真正有意义的项目,希望能推进公司往深度学习方面来发展。另外技术方面专注于数学和深度学习。具体的计划就不列了,因为貌似每次列出来也不一定能全部完成 =v=

ssh的反向连接

家里有个树莓派,基本上用来当了一个微型服务器,24小时跑着。主要用途是在我不在家的时候控制家里网络,充分利用带宽。一直以来,我都是用树莓派把IP更新到DDNS(家里是外网IP),并且在路由器上做端口映射。这样就能在其他地方访问树莓派了。但是不知为何,这段时间无法从外网ping通家里(后来发现是光猫问题,换了个猫就好了),从外围直接联通家里的树莓派这条路被封了,所以就折腾了SSH的反向连接功能。用这个方法也可以克服家里是内网IP的情况,当然前提是我们在外网有一台服务器或者vps。原理上和其他的反向连接是一样的,这里就不讨论原理了,直接介绍方法。

# ssh -fCNR remote_port:localhost:local_port user@remote_addr
-f 后台执行ssh指令
-C 允许压缩数据
-N 不执行远程指令
-R 将远程主机(服务器)的某个端口转发到本地端指定机器的指定端口

这条命令的意思是:请将remote_addr机器里对remote_port的连接转发到本地机器的本地端口。举个例子,假设我有一台内网机器A,其ssh端口为6622,另外有一台VPS,地址是VPS_ADDR.com,SSH端口是8822。这个时候我想通过VPS的9922端口去访问树莓派的SSH。那么我需要用命令:

# ssh -fCNR 9922:localhost:6622 user@VPS_ADDR.com

当完成了这个命令后,我们就可以在VPS上登陆树莓派了:

# ssh -p 9922 pi@localhost

解决了这个问题后,我们需要解决另外一个问题。我们每次输入反向连接的命令的时候总需要输入VPS密码,这个非常不利于我们把命令设置为开机启动。为了解决这个问题,首先想到的是sshpass。这个工具可以帮助我们自动输入密码,命令如下:

# sshpass -p your_VPS_password ssh -fCNR 9922:localhost:6622 user@VPS_ADDR.com

把命令放到树莓派的开机启动命令后,重启树莓派,成功从VPS连入了树莓派。不过第二天,我又发现了另外一个问题,SSH的反向连接在无人访问的时候会超时,超时后会断开链接。这样明显不符合我想连就连的需求。于是就得上autossh这个工具了,这个工具会守护ssh,让ssh的反向连接保持联通。但是autossh不支持自动输入密码,网上很多解决办法是用证书的方式省去了输入密码的过程,不过我这里提供另外一个方法,使用expect工具。

# expect -c "
spawn autossh -M 11122 -CNR 9922:localhost:6622 user@VPS_ADDR.com -o StrictHostKeyChecking=no
expect \"Password:\"
send \"your_password\r\"
expect eof
"

这个工具可以自动和ssh进行交互,当VPS提示Password:的时候,我们send密码过去就行了,注意使用StrictHostKeyChecking=no来避免Host key的验证提示。把这个命令放在开机启动的时候执行,完美解决了外网访问家里树莓派的问题。

将图片转化成mnist格式

利用mnist数据对数字符号进行识别基本上算是深度学习的Hello World了。在我学习这个“hello world”的过程中,多多少少有点不爽,原因是无论是训练数据还是测试数据,都是mnist准备好的,即使最后训练的数字识别率很高,我也没有什么参与感。其实,我特别想测试自己手写数字的识别率。
为了完成上述想法,我第一个想到的是将普通图片数据转换成mnist数据。mnist的数据格式非常简单,如下图所示:

2017-12-10-t10k-images.idx3-ubyte

2017-12-10-t10k-labels.idx1-ubyte

两幅图分别表示了图形数据和标签数据。他们都有一个4字节长度的magic number,用来识别数据的具体格式。如果是标签数据,那么格式相对简单,后续是一个标签数量,接着的是标签数据(0-9的值)。如果是图像数据,那么magic number后,出了4个字节的数据数量以外,还有分别占4字节的行列数据,最后的就是图像数据。结构非常简单,但是值得注意的事情有两点:

  1. 数据使用big endian组织的。
  2. 图像数据中,255表示前景,也就是黑色,0表示背景,也就是白色,这和我们平时看到的RGB是不同的。

知道了数据格式,接下来的事情就是用程序将图像转换到mnist了。说实在的,如果对于操作二进制的数据,C比python还是方便不少的。但是C读取图像更加麻烦。所以这里推荐还是使用python对数据做转化。

# 首先导入图像处理库
from PIL import Image
from array import *
# 接下来打开图片,并且将像转化为8bit黑白像素
im = Image.open(path_to_image)
im = im.convert('L')
# 转换图像到mnist的大小28*28
im = im.resize((28,28))
# 获取图像长宽
width, height = im.size()
# 将图像数据转化位mnist
for x in range(0,width):
for y in range(0,height):
data_image.append(255 - pixel[y,x])

# 将数据写到mnist文件中
header = array('B')
# 写入magic number
header.extend([0,0,8,3])
# 写入数据数量,以一个图片为例
header.extend([0,0,0,1])
# 写入行列像素数
header.extend([0,0,0,28])
header.extend([0,0,0,28])
# 写入数据
header.extend(data_image)
# 最后写文件
output_file = open(r'data.mnist', 'wb')
header.tofile(output_file)
output_file.close()

以上是对图像数据的转换,标签数据的转换代码和以上代码基本一样,所以这里不再赘述。
有了这个方法,我们可以通过画图软件画上一堆自己手写的数字,通过python批量转化成mnist格式的数据,再让tensorflow进行测试,算出模型对我们自己手写数字的识别正确率。

好了,以上就是我说的第一种让模型识别自己的手写数字的方法。不过,这个方法不能实时的识别我手写的数字,让人总觉得缺点什么。于是就有了第二种方法,这种方法将借助浏览器,js以及web server等工具将手写的数字实时的传给后台的模型进行识别,然后把结果回复给用户。不过这个方法就要等待下一篇文章来描述了。

TensorFlow pip安装(非GPU版)

TensorFlow安装有很多种方法,其中用dock安装是最方便的方式,源代码编译是最麻烦但是最能切合自己机器CPU的方法,当然了,你也可以用Anacaonda安装(一个科学计算的工具合集,以后有机会再介绍)。不过这次,我将介绍使用pip安装的方法。我个人认为这种方法其实是众多方法中最简单方便的方法了(系统基于ubuntu 16.04)。

先来说说常规用pip安装的方法:

pip3 install --upgrade tensorflow

首先当然是安装tensorflow框架了(当然你得有python,如果没有可以使用apt-get install python3-pip python3-dev来安装)

接下来就是安装temsorflow了

pip3 install tensorflow     # Python 3.n; CPU support (no GPU support)

如果你的电脑和系统的配置正常,这个时候应该可以正常使用tensorflow了。不过,在我的电脑上出现了这么一个警告:

Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA

很明显,现代CPU支持折现特性已经很常见了,但是框架版本并不支持。难道真的要便宜一个么?(据我测试2G内存的机器是无法编译tensorflow的)。于是物品翻阅了github,发现以及有人走在前面,在 https://github.com/mind/wheels 里有很多已经编译的模块提供ubuntu。比如,在我的平台上适合的版本是

https://github.com/mind/wheels/releases/download/tf1.4-cpu/tensorflow-1.4.0-cp35-cp35m-linux_x86_64.whl

那么就可以使用这个版本来安装tensorflow:

pip --no-cache-dir install https://github.com/mind/wheels/releases/download/tf1.4-cpu/tensorflow-1.4.0-cp35-cp35m-linux_x86_64.whl